Extract aircraft SVG icons to external files and add dynamic marker updates
- Extract all aircraft type SVG designs to separate files in /static/icons/ - Add icon caching system with async loading and fallback mechanisms - Implement dynamic marker icon updates when aircraft properties change - Detect and respond to aircraft type/category, ground status, and rotation changes - Use currentColor in SVG files for dynamic color application - Maintain performance with intelligent change detection (5° rotation threshold) - Support real-time marker updates for weight class transitions and ADS-B changes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
da4645d483
commit
43e55b2ba0
7 changed files with 147 additions and 59 deletions
9
assets/static/icons/cargo.svg
Normal file
9
assets/static/icons/cargo.svg
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
<!-- Wide-body cargo aircraft shape -->
|
||||||
|
<path d="M0,-12 L-10,8 L-3,8 L0,12 L3,8 L10,8 Z" fill="currentColor"/>
|
||||||
|
<!-- Fuselage bulk -->
|
||||||
|
<rect x="-2" y="-6" width="4" height="8" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 384 B |
7
assets/static/icons/commercial.svg
Normal file
7
assets/static/icons/commercial.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
<!-- Standard commercial aircraft shape -->
|
||||||
|
<path d="M0,-12 L-8,8 L-2,8 L0,12 L2,8 L8,8 Z" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 292 B |
7
assets/static/icons/ga.svg
Normal file
7
assets/static/icons/ga.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
<!-- Small general aviation aircraft -->
|
||||||
|
<path d="M0,-10 L-5,6 L-1,6 L0,10 L1,6 L5,6 Z" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 289 B |
10
assets/static/icons/ground.svg
Normal file
10
assets/static/icons/ground.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
<!-- Ground vehicle - truck/car shape -->
|
||||||
|
<rect x="-6" y="-4" width="12" height="8" fill="currentColor" rx="2"/>
|
||||||
|
<!-- Wheels -->
|
||||||
|
<circle cx="-3" cy="2" r="2" fill="#333"/>
|
||||||
|
<circle cx="3" cy="2" r="2" fill="#333"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |
12
assets/static/icons/helicopter.svg
Normal file
12
assets/static/icons/helicopter.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
<!-- Rotor disc -->
|
||||||
|
<circle cx="0" cy="0" r="10" fill="none" stroke="currentColor" stroke-width="1" opacity="0.3"/>
|
||||||
|
<!-- Main body -->
|
||||||
|
<path d="M0,-8 L-6,6 L-1,6 L0,8 L1,6 L6,6 Z" fill="currentColor"/>
|
||||||
|
<!-- Rotor mast -->
|
||||||
|
<path d="M0,-6 L0,-10" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M0,6 L0,8" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 546 B |
7
assets/static/icons/military.svg
Normal file
7
assets/static/icons/military.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
<!-- Swept-wing fighter jet shape -->
|
||||||
|
<path d="M0,-12 L-4,2 L-8,8 L-2,6 L0,12 L2,6 L8,8 L4,2 Z" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 297 B |
|
|
@ -12,9 +12,67 @@ export class AircraftManager {
|
||||||
this.markerUpdateCount = 0;
|
this.markerUpdateCount = 0;
|
||||||
this.markerRemoveCount = 0;
|
this.markerRemoveCount = 0;
|
||||||
|
|
||||||
|
// SVG icon cache
|
||||||
|
this.iconCache = new Map();
|
||||||
|
this.loadIcons();
|
||||||
|
|
||||||
// Map event listeners removed - let Leaflet handle positioning naturally
|
// Map event listeners removed - let Leaflet handle positioning naturally
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadIcons() {
|
||||||
|
const iconTypes = ['commercial', 'helicopter', 'military', 'cargo', 'ga', 'ground'];
|
||||||
|
|
||||||
|
for (const type of iconTypes) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/static/icons/${type}.svg`);
|
||||||
|
const svgText = await response.text();
|
||||||
|
this.iconCache.set(type, svgText);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load icon for ${type}:`, error);
|
||||||
|
// Fallback to inline SVG if needed
|
||||||
|
this.iconCache.set(type, this.createFallbackIcon(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createFallbackIcon(type) {
|
||||||
|
// Fallback inline SVG if file loading fails
|
||||||
|
const color = 'currentColor';
|
||||||
|
let path = '';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'helicopter':
|
||||||
|
path = `<circle cx="0" cy="0" r="10" fill="none" stroke="${color}" stroke-width="1" opacity="0.3"/>
|
||||||
|
<path d="M0,-8 L-6,6 L-1,6 L0,8 L1,6 L6,6 Z" fill="${color}"/>
|
||||||
|
<path d="M0,-6 L0,-10" stroke="${color}" stroke-width="2"/>
|
||||||
|
<path d="M0,6 L0,8" stroke="${color}" stroke-width="2"/>`;
|
||||||
|
break;
|
||||||
|
case 'military':
|
||||||
|
path = `<path d="M0,-12 L-4,2 L-8,8 L-2,6 L0,12 L2,6 L8,8 L4,2 Z" fill="${color}"/>`;
|
||||||
|
break;
|
||||||
|
case 'cargo':
|
||||||
|
path = `<path d="M0,-12 L-10,8 L-3,8 L0,12 L3,8 L10,8 Z" fill="${color}"/>
|
||||||
|
<rect x="-2" y="-6" width="4" height="8" fill="${color}"/>`;
|
||||||
|
break;
|
||||||
|
case 'ga':
|
||||||
|
path = `<path d="M0,-10 L-5,6 L-1,6 L0,10 L1,6 L5,6 Z" fill="${color}"/>`;
|
||||||
|
break;
|
||||||
|
case 'ground':
|
||||||
|
path = `<rect x="-6" y="-4" width="12" height="8" fill="${color}" rx="2"/>
|
||||||
|
<circle cx="-3" cy="2" r="2" fill="#333"/>
|
||||||
|
<circle cx="3" cy="2" r="2" fill="#333"/>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
path = `<path d="M0,-12 L-8,8 L-2,8 L0,12 L2,8 L8,8 Z" fill="${color}"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(16,16)">
|
||||||
|
${path}
|
||||||
|
</g>
|
||||||
|
</svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
updateAircraftData(data) {
|
updateAircraftData(data) {
|
||||||
if (data.aircraft) {
|
if (data.aircraft) {
|
||||||
|
|
@ -74,13 +132,21 @@ export class AircraftManager {
|
||||||
const oldPos = marker.getLatLng();
|
const oldPos = marker.getLatLng();
|
||||||
marker.setLatLng(pos);
|
marker.setLatLng(pos);
|
||||||
|
|
||||||
// Update icon if track has changed to apply new rotation
|
// Check if icon needs to be updated (track rotation, aircraft type, or ground status changes)
|
||||||
if (aircraft.Track !== undefined) {
|
const currentRotation = marker._currentRotation || 0;
|
||||||
const currentRotation = marker._currentRotation || 0;
|
const currentType = marker._currentType || null;
|
||||||
if (Math.abs(currentRotation - aircraft.Track) > 5) { // Update if rotation changed by more than 5 degrees
|
const currentOnGround = marker._currentOnGround || false;
|
||||||
marker.setIcon(this.createAircraftIcon(aircraft));
|
|
||||||
marker._currentRotation = aircraft.Track;
|
const newType = this.getAircraftIconType(aircraft);
|
||||||
}
|
const rotationChanged = aircraft.Track !== undefined && Math.abs(currentRotation - aircraft.Track) > 5;
|
||||||
|
const typeChanged = currentType !== newType;
|
||||||
|
const groundStatusChanged = currentOnGround !== aircraft.OnGround;
|
||||||
|
|
||||||
|
if (rotationChanged || typeChanged || groundStatusChanged) {
|
||||||
|
marker.setIcon(this.createAircraftIcon(aircraft));
|
||||||
|
marker._currentRotation = aircraft.Track || 0;
|
||||||
|
marker._currentType = newType;
|
||||||
|
marker._currentOnGround = aircraft.OnGround || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle popup exactly like Leaflet expects
|
// Handle popup exactly like Leaflet expects
|
||||||
|
|
@ -99,8 +165,10 @@ export class AircraftManager {
|
||||||
icon: icon
|
icon: icon
|
||||||
}).addTo(this.map);
|
}).addTo(this.map);
|
||||||
|
|
||||||
// Store current rotation for future updates
|
// Store current properties for future update comparisons
|
||||||
marker._currentRotation = aircraft.Track || 0;
|
marker._currentRotation = aircraft.Track || 0;
|
||||||
|
marker._currentType = this.getAircraftIconType(aircraft);
|
||||||
|
marker._currentOnGround = aircraft.OnGround || false;
|
||||||
|
|
||||||
marker.bindPopup(this.createPopupContent(aircraft), {
|
marker.bindPopup(this.createPopupContent(aircraft), {
|
||||||
maxWidth: 450,
|
maxWidth: 450,
|
||||||
|
|
@ -133,60 +201,28 @@ export class AircraftManager {
|
||||||
const size = aircraft.OnGround ? 12 : 16;
|
const size = aircraft.OnGround ? 12 : 16;
|
||||||
const rotation = aircraft.Track || 0;
|
const rotation = aircraft.Track || 0;
|
||||||
|
|
||||||
// Create different SVG shapes based on aircraft type
|
// Get SVG template from cache
|
||||||
let aircraftPath;
|
let svgTemplate = this.iconCache.get(iconType) || this.iconCache.get('commercial');
|
||||||
|
|
||||||
switch (iconType) {
|
if (!svgTemplate) {
|
||||||
case 'helicopter':
|
// Ultimate fallback - create a simple circle
|
||||||
// Helicopter shape with rotor disc
|
svgTemplate = `<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
aircraftPath = `
|
<g transform="translate(16,16)">
|
||||||
<circle cx="0" cy="0" r="10" fill="none" stroke="${color}" stroke-width="1" opacity="0.3"/>
|
<circle cx="0" cy="0" r="8" fill="currentColor"/>
|
||||||
<path d="M0,-8 L-6,6 L-1,6 L0,8 L1,6 L6,6 Z" fill="${color}"/>
|
</g>
|
||||||
<path d="M0,-6 L0,-10" stroke="${color}" stroke-width="2"/>
|
</svg>`;
|
||||||
<path d="M0,6 L0,8" stroke="${color}" stroke-width="2"/>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case 'military':
|
|
||||||
// Swept-wing fighter jet shape
|
|
||||||
aircraftPath = `
|
|
||||||
<path d="M0,-12 L-4,2 L-8,8 L-2,6 L0,12 L2,6 L8,8 L4,2 Z" fill="${color}"/>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case 'cargo':
|
|
||||||
// Wide-body cargo aircraft shape
|
|
||||||
aircraftPath = `
|
|
||||||
<path d="M0,-12 L-10,8 L-3,8 L0,12 L3,8 L10,8 Z" fill="${color}"/>
|
|
||||||
<rect x="-2" y="-6" width="4" height="8" fill="${color}"/>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case 'ga':
|
|
||||||
// Small general aviation aircraft
|
|
||||||
aircraftPath = `
|
|
||||||
<path d="M0,-10 L-5,6 L-1,6 L0,10 L1,6 L5,6 Z" fill="${color}"/>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case 'ground':
|
|
||||||
// Ground vehicle - simplified truck/car shape
|
|
||||||
aircraftPath = `
|
|
||||||
<rect x="-6" y="-4" width="12" height="8" fill="${color}" rx="2"/>
|
|
||||||
<circle cx="-3" cy="2" r="2" fill="#333"/>
|
|
||||||
<circle cx="3" cy="2" r="2" fill="#333"/>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Default commercial aircraft shape
|
|
||||||
aircraftPath = `
|
|
||||||
<path d="M0,-12 L-8,8 L-2,8 L0,12 L2,8 L8,8 Z" fill="${color}"/>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const svg = `
|
// Apply color and rotation to the SVG
|
||||||
<svg width="${size * 2}" height="${size * 2}" viewBox="0 0 32 32">
|
let svg = svgTemplate
|
||||||
<g transform="translate(16,16) rotate(${rotation})">
|
.replace(/currentColor/g, color)
|
||||||
${aircraftPath}
|
.replace(/width="32"/, `width="${size * 2}"`)
|
||||||
</g>
|
.replace(/height="32"/, `height="${size * 2}"`);
|
||||||
</svg>
|
|
||||||
`;
|
// Add rotation to the transform
|
||||||
|
if (rotation !== 0) {
|
||||||
|
svg = svg.replace(/transform="translate\(16,16\)"/, `transform="translate(16,16) rotate(${rotation})"`);
|
||||||
|
}
|
||||||
|
|
||||||
return L.divIcon({
|
return L.divIcon({
|
||||||
html: svg,
|
html: svg,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue