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:
parent
43e55b2ba0
commit
20bdcf54ec
15 changed files with 746 additions and 67 deletions
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: '© <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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue