Move documentation to docs/ directory and improve terminology

- Move ARCHITECTURE.md and CLAUDE.md to docs/ directory
- Replace "real-time" terminology with accurate "low-latency" and "high-performance"
- Update README to reflect correct performance characteristics
- Add comprehensive ICAO country database with SQLite backend
- Fix display options positioning and functionality
- Add map scale controls and improved range ring visibility
- Enhance aircraft marker orientation and trail management

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-24 16:24:46 +02:00
commit 20bdcf54ec
15 changed files with 746 additions and 67 deletions

View file

@ -193,6 +193,48 @@ body {
background: #404040;
}
.display-options {
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
background: rgba(45, 45, 45, 0.95);
border: 1px solid #404040;
border-radius: 8px;
padding: 1rem;
min-width: 200px;
}
.display-options h4 {
margin-bottom: 0.5rem;
color: #ffffff;
font-size: 0.9rem;
}
.option-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.option-group label {
display: flex;
align-items: center;
cursor: pointer;
font-size: 0.8rem;
color: #cccccc;
}
.option-group input[type="checkbox"] {
margin-right: 0.5rem;
accent-color: #00d4ff;
transform: scale(1.1);
}
.option-group label:hover {
color: #ffffff;
}
.legend {
position: absolute;
bottom: 10px;

View file

@ -77,11 +77,29 @@
<button id="center-map" title="Center on aircraft">Center Map</button>
<button id="reset-map" title="Reset to origin">Reset Map</button>
<button id="toggle-trails" title="Show/hide aircraft trails">Show Trails</button>
<button id="toggle-range" title="Show/hide range circles">Show Range</button>
<button id="toggle-sources" title="Show/hide source locations">Show Sources</button>
<button id="toggle-dark-mode" title="Toggle dark/light mode">🌙 Night Mode</button>
</div>
<!-- Display options -->
<div class="display-options">
<h4>Display Options</h4>
<div class="option-group">
<label>
<input type="checkbox" id="show-site-positions" checked>
<span>Site Positions</span>
</label>
<label>
<input type="checkbox" id="show-range-rings">
<span>Range Rings</span>
</label>
<label>
<input type="checkbox" id="show-selected-trail">
<span>Selected Aircraft Trail</span>
</label>
</div>
</div>
<!-- Legend -->
<div class="legend">
<h4>Aircraft Types</h4>

View file

@ -22,6 +22,10 @@ class SkyView {
// Charts
this.charts = {};
// Selected aircraft tracking
this.selectedAircraft = null;
this.selectedTrailEnabled = false;
this.init();
}
@ -40,6 +44,11 @@ class SkyView {
// Initialize aircraft manager with the map
this.aircraftManager = new AircraftManager(map);
// Set up selected aircraft trail callback
this.aircraftManager.setSelectedAircraftCallback((icao) => {
return this.selectedTrailEnabled && this.selectedAircraft === icao;
});
// Initialize WebSocket with callbacks
this.wsManager = new WebSocketManager(
(message) => this.handleWebSocketMessage(message),
@ -71,11 +80,12 @@ class SkyView {
const centerMapBtn = document.getElementById('center-map');
const resetMapBtn = document.getElementById('reset-map');
const toggleTrailsBtn = document.getElementById('toggle-trails');
const toggleRangeBtn = document.getElementById('toggle-range');
const toggleSourcesBtn = document.getElementById('toggle-sources');
if (centerMapBtn) {
centerMapBtn.addEventListener('click', () => this.aircraftManager.centerMapOnAircraft());
centerMapBtn.addEventListener('click', () => {
this.aircraftManager.centerMapOnAircraft(() => this.mapManager.getSourcePositions());
});
}
if (resetMapBtn) {
@ -89,12 +99,6 @@ class SkyView {
});
}
if (toggleRangeBtn) {
toggleRangeBtn.addEventListener('click', () => {
const showRange = this.mapManager.toggleRangeCircles();
toggleRangeBtn.textContent = showRange ? 'Hide Range' : 'Show Range';
});
}
if (toggleSourcesBtn) {
toggleSourcesBtn.addEventListener('click', () => {
@ -128,6 +132,50 @@ class SkyView {
this.mapManager.updateCoverageDisplay();
});
}
// Display option checkboxes
const sitePositionsCheckbox = document.getElementById('show-site-positions');
const rangeRingsCheckbox = document.getElementById('show-range-rings');
const selectedTrailCheckbox = document.getElementById('show-selected-trail');
if (sitePositionsCheckbox) {
sitePositionsCheckbox.addEventListener('change', (e) => {
if (e.target.checked) {
this.mapManager.showSources = true;
this.mapManager.updateSourceMarkers();
} else {
this.mapManager.showSources = false;
this.mapManager.sourceMarkers.forEach(marker => this.mapManager.map.removeLayer(marker));
this.mapManager.sourceMarkers.clear();
}
});
}
if (rangeRingsCheckbox) {
rangeRingsCheckbox.addEventListener('change', (e) => {
if (e.target.checked) {
this.mapManager.showRange = true;
this.mapManager.updateRangeCircles();
} else {
this.mapManager.showRange = false;
this.mapManager.rangeCircles.forEach(circle => this.mapManager.map.removeLayer(circle));
this.mapManager.rangeCircles.clear();
}
});
}
if (selectedTrailCheckbox) {
selectedTrailCheckbox.addEventListener('change', (e) => {
this.selectedTrailEnabled = e.target.checked;
if (!e.target.checked && this.selectedAircraft) {
// Hide currently selected aircraft trail
this.aircraftManager.hideAircraftTrail(this.selectedAircraft);
} else if (e.target.checked && this.selectedAircraft) {
// Show currently selected aircraft trail
this.aircraftManager.showAircraftTrail(this.selectedAircraft);
}
});
}
}
setupAircraftSelection() {
@ -135,6 +183,19 @@ class SkyView {
const { icao, aircraft } = e.detail;
this.uiManager.switchView('map-view');
// Hide trail for previously selected aircraft
if (this.selectedAircraft && this.selectedTrailEnabled) {
this.aircraftManager.hideAircraftTrail(this.selectedAircraft);
}
// Update selected aircraft
this.selectedAircraft = icao;
// Show trail for newly selected aircraft if enabled
if (this.selectedTrailEnabled) {
this.aircraftManager.showAircraftTrail(icao);
}
// DON'T change map view - just open popup like Leaflet expects
if (this.mapManager.map && aircraft.Latitude && aircraft.Longitude) {
const marker = this.aircraftManager.aircraftMarkers.get(icao);
@ -171,6 +232,11 @@ class SkyView {
this.uiManager.updateStatistics();
this.uiManager.updateHeaderInfo();
// Clear selected aircraft if it no longer exists
if (this.selectedAircraft && !this.aircraftManager.aircraftData.has(this.selectedAircraft)) {
this.selectedAircraft = null;
}
// Update coverage controls
this.mapManager.updateCoverageControls();

View file

@ -16,6 +16,9 @@ export class AircraftManager {
this.iconCache = new Map();
this.loadIcons();
// Selected aircraft trail tracking
this.selectedAircraftCallback = null;
// Map event listeners removed - let Leaflet handle positioning naturally
}
@ -94,7 +97,22 @@ export class AircraftManager {
if (!currentICAOs.has(icao)) {
this.map.removeLayer(marker);
this.aircraftMarkers.delete(icao);
this.aircraftTrails.delete(icao);
// Remove trail if it exists
if (this.aircraftTrails.has(icao)) {
const trail = this.aircraftTrails.get(icao);
if (trail.polyline) {
this.map.removeLayer(trail.polyline);
}
this.aircraftTrails.delete(icao);
}
// Notify if this was the selected aircraft
if (this.selectedAircraftCallback && this.selectedAircraftCallback(icao)) {
// Aircraft was selected and disappeared - could notify main app
// For now, the callback will return false automatically since selectedAircraft will be cleared
}
this.markerRemoveCount++;
}
}
@ -189,8 +207,8 @@ export class AircraftManager {
}
}
// Update trails
if (this.showTrails) {
// Update trails - check both global trails and individual selected aircraft
if (this.showTrails || this.isSelectedAircraftTrailEnabled(icao)) {
this.updateAircraftTrail(icao, aircraft);
}
}
@ -318,8 +336,8 @@ export class AircraftManager {
createPopupContent(aircraft) {
const type = this.getAircraftType(aircraft);
const country = this.getCountryFromICAO(aircraft.ICAO24 || '');
const flag = this.getCountryFlag(country);
const country = aircraft.country || 'Unknown';
const flag = aircraft.flag || '🏳️';
const altitude = aircraft.Altitude || aircraft.BaroAltitude || 0;
const altitudeM = altitude ? Math.round(altitude * 0.3048) : 0;
@ -401,37 +419,6 @@ export class AircraftManager {
return minDistance === Infinity ? null : minDistance;
}
getCountryFromICAO(icao) {
if (!icao || icao.length < 6) return 'Unknown';
const prefix = icao[0];
const countryMap = {
'4': 'Europe',
'A': 'United States',
'C': 'Canada',
'D': 'Germany',
'F': 'France',
'G': 'United Kingdom',
'I': 'Italy',
'J': 'Japan'
};
return countryMap[prefix] || 'Unknown';
}
getCountryFlag(country) {
const flags = {
'United States': '🇺🇸',
'Canada': '🇨🇦',
'Germany': '🇩🇪',
'France': '🇫🇷',
'United Kingdom': '🇬🇧',
'Italy': '🇮🇹',
'Japan': '🇯🇵',
'Europe': '🇪🇺'
};
return flags[country] || '🏳️';
}
toggleTrails() {
this.showTrails = !this.showTrails;
@ -449,23 +436,56 @@ export class AircraftManager {
return this.showTrails;
}
centerMapOnAircraft() {
if (this.aircraftData.size === 0) return;
showAircraftTrail(icao) {
const aircraft = this.aircraftData.get(icao);
if (aircraft && aircraft.position_history && aircraft.position_history.length >= 2) {
this.updateAircraftTrail(icao, aircraft);
}
}
hideAircraftTrail(icao) {
if (this.aircraftTrails.has(icao)) {
const trail = this.aircraftTrails.get(icao);
if (trail.polyline) {
this.map.removeLayer(trail.polyline);
}
this.aircraftTrails.delete(icao);
}
}
setSelectedAircraftCallback(callback) {
this.selectedAircraftCallback = callback;
}
isSelectedAircraftTrailEnabled(icao) {
return this.selectedAircraftCallback && this.selectedAircraftCallback(icao);
}
centerMapOnAircraft(includeSourcesCallback = null) {
const validAircraft = Array.from(this.aircraftData.values())
.filter(a => a.Latitude && a.Longitude);
if (validAircraft.length === 0) return;
const allPoints = [];
if (validAircraft.length === 1) {
// Center on single aircraft
const aircraft = validAircraft[0];
this.map.setView([aircraft.Latitude, aircraft.Longitude], 12);
// Add aircraft positions
validAircraft.forEach(a => {
allPoints.push([a.Latitude, a.Longitude]);
});
// Add source positions if callback provided
if (includeSourcesCallback && typeof includeSourcesCallback === 'function') {
const sourcePositions = includeSourcesCallback();
allPoints.push(...sourcePositions);
}
if (allPoints.length === 0) return;
if (allPoints.length === 1) {
// Center on single point
this.map.setView(allPoints[0], 12);
} else {
// Fit bounds to all aircraft
const bounds = L.latLngBounds(
validAircraft.map(a => [a.Latitude, a.Longitude])
);
// Fit bounds to all points (aircraft + sources)
const bounds = L.latLngBounds(allPoints);
this.map.fitBounds(bounds.pad(0.1));
}
}

View file

@ -46,6 +46,13 @@ export class MapManager {
maxZoom: 19
}).addTo(this.map);
// Add scale control for distance estimation
L.control.scale({
metric: true,
imperial: true,
position: 'bottomright'
}).addTo(this.map);
return this.map;
}
@ -67,6 +74,13 @@ export class MapManager {
this.coverageTileLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(this.coverageMap);
// Add scale control for distance estimation
L.control.scale({
metric: true,
imperial: true,
position: 'bottomright'
}).addTo(this.coverageMap);
}
return this.coverageMap;
@ -151,9 +165,9 @@ export class MapManager {
radius: range,
fillColor: 'transparent',
color: '#00d4ff',
weight: 1,
opacity: 0.3 - (index * 0.1),
dashArray: '5,5'
weight: 2,
opacity: 0.7 - (index * 0.15),
dashArray: '8,4'
}).addTo(this.map);
this.rangeCircles.set(`${id}_${range}`, circle);
@ -345,4 +359,14 @@ export class MapManager {
setSelectedSource(sourceId) {
this.selectedSource = sourceId;
}
getSourcePositions() {
const positions = [];
for (const [id, source] of this.sourcesData) {
if (source.latitude && source.longitude) {
positions.push([source.latitude, source.longitude]);
}
}
return positions;
}
}