Complete Beast format implementation with enhanced features and fixes #19

Merged
olemd merged 38 commits from beast-format-refactor into main 2025-08-24 20:50:38 +02:00
4 changed files with 66 additions and 37 deletions
Showing only changes of commit b527f5a8ee - Show all commits

Switch to light map theme by default with dark mode toggle

Major improvements to map theming and aircraft type display:

Map Theme Changes:
- Changed default map from dark to light theme (CartoDB Positron)
- Added night mode toggle button with sun/moon icons
- Both main map and coverage map now switch themes together
- Light theme provides better daylight visibility

Aircraft Type Display:
- Now displays actual ADS-B category directly (e.g., "Medium 34000-136000kg")
- Removed guessing/interpretation of aircraft types
- Icons still use simplified categories for visual distinction
- More accurate and standards-compliant display

This provides a cleaner, more professional appearance with the light map
and gives users accurate ADS-B category information instead of interpreted types.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Ole-Morten Duesund 2025-08-24 15:09:54 +02:00

View file

@ -79,6 +79,7 @@
<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>
<!-- Legend -->

View file

@ -103,6 +103,14 @@ class SkyView {
});
}
const toggleDarkModeBtn = document.getElementById('toggle-dark-mode');
if (toggleDarkModeBtn) {
toggleDarkModeBtn.addEventListener('click', () => {
const isDarkMode = this.mapManager.toggleDarkMode();
toggleDarkModeBtn.innerHTML = isDarkMode ? '☀️ Light Mode' : '🌙 Night Mode';
});
}
// Coverage controls
const toggleHeatmapBtn = document.getElementById('toggle-heatmap');
const coverageSourceSelect = document.getElementById('coverage-source');

View file

@ -129,14 +129,14 @@ export class AircraftManager {
}
createAircraftIcon(aircraft) {
const type = this.getAircraftType(aircraft);
const color = this.getAircraftColor(type);
const iconType = this.getAircraftIconType(aircraft);
const color = this.getAircraftColor(iconType);
const size = aircraft.OnGround ? 12 : 16;
// Create different SVG shapes based on aircraft type
let aircraftPath;
switch (type) {
switch (iconType) {
case 'helicopter':
// Helicopter shape with rotor disc
aircraftPath = `
@ -197,44 +197,29 @@ export class AircraftManager {
}
getAircraftType(aircraft) {
// For display purposes, return the actual ADS-B category
// This is used in the popup display
if (aircraft.OnGround) return 'On Ground';
if (aircraft.Category) return aircraft.Category;
return 'Unknown';
}
getAircraftIconType(aircraft) {
// For icon selection, we still need basic categories
// This determines which SVG shape to use
if (aircraft.OnGround) return 'ground';
// Use ADS-B Category field for proper aircraft classification
if (aircraft.Category) {
const cat = aircraft.Category.toLowerCase();
// Standard ADS-B aircraft categories - check specific terms first
if (cat.includes('helicopter') || cat.includes('rotorcraft') || cat.includes('gyrocopter')) return 'helicopter';
// Map to basic icon types for visual representation
if (cat.includes('helicopter') || cat.includes('rotorcraft')) return 'helicopter';
if (cat.includes('military') || cat.includes('fighter') || cat.includes('bomber')) return 'military';
// ADS-B weight-based categories (RTCA DO-260B standard)
if (cat.includes('heavy') || cat.includes('super') || cat.includes('136000kg')) return 'cargo'; // Heavy/Super category
if (cat.includes('light') || cat.includes('15500kg') || cat.includes('5700kg') || cat.includes('glider') || cat.includes('ultralight') || cat.includes('sport')) return 'ga'; // Light categories
if (cat.includes('medium') || cat.includes('34000kg')) return 'commercial'; // Medium category - typically commercial airliners
// Specific aircraft type categories
if (cat.includes('cargo') || cat.includes('transport') || cat.includes('freighter')) return 'cargo';
if (cat.includes('airliner') || cat.includes('jet') || cat.includes('turboprop')) return 'commercial';
if (cat.includes('cargo') || cat.includes('heavy') || cat.includes('super')) return 'cargo';
if (cat.includes('light') || cat.includes('glider') || cat.includes('ultralight')) return 'ga';
}
// Fallback to callsign analysis for classification
if (aircraft.Callsign) {
const cs = aircraft.Callsign.toLowerCase();
// Helicopter identifiers
if (cs.includes('heli') || cs.includes('rescue') || cs.includes('medevac') || cs.includes('lifeguard') ||
cs.includes('police') || cs.includes('sheriff') || cs.includes('medic')) return 'helicopter';
// Military identifiers
if (cs.includes('mil') || cs.includes('army') || cs.includes('navy') || cs.includes('air force') ||
cs.includes('usaf') || cs.includes('usmc') || cs.includes('uscg')) return 'military';
// Cargo identifiers
if (cs.includes('cargo') || cs.includes('fedex') || cs.includes('ups') || cs.includes('dhl') ||
cs.includes('freight') || cs.includes('express')) return 'cargo';
}
// Default to commercial for unclassified aircraft
// Default commercial icon for everything else
return 'commercial';
}
@ -306,7 +291,7 @@ export class AircraftManager {
<strong>Country:</strong> ${country}
</div>
<div class="detail-row">
<strong>Type:</strong> ${type.charAt(0).toUpperCase() + type.slice(1)}
<strong>Type:</strong> ${type}
</div>
<div class="detail-grid">

View file

@ -15,6 +15,11 @@ export class MapManager {
// Data references
this.sourcesData = new Map();
// Map theme
this.isDarkMode = false;
this.currentTileLayer = null;
this.coverageTileLayer = null;
}
async initializeMap() {
@ -34,8 +39,8 @@ export class MapManager {
this.map = L.map('map').setView([origin.latitude, origin.longitude], 10);
// Dark tile layer
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
// Light tile layer by default
this.currentTileLayer = 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 &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 19
@ -59,7 +64,7 @@ export class MapManager {
this.coverageMap = L.map('coverage-map').setView([origin.latitude, origin.longitude], 10);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
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);
}
@ -223,6 +228,36 @@ export class MapManager {
return this.showSources;
}
toggleDarkMode() {
this.isDarkMode = !this.isDarkMode;
const lightUrl = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
const darkUrl = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png';
const tileUrl = this.isDarkMode ? darkUrl : lightUrl;
const tileOptions = {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 19
};
// Update main map
if (this.map && this.currentTileLayer) {
this.map.removeLayer(this.currentTileLayer);
this.currentTileLayer = L.tileLayer(tileUrl, tileOptions).addTo(this.map);
}
// Update coverage map
if (this.coverageMap && this.coverageTileLayer) {
this.coverageMap.removeLayer(this.coverageTileLayer);
this.coverageTileLayer = L.tileLayer(tileUrl, {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(this.coverageMap);
}
return this.isDarkMode;
}
// Coverage map methods
updateCoverageControls() {
const select = document.getElementById('coverage-source');