feat: add interactive exploration of Shannon's capacity formula with Plotly graphs
All checks were successful
Build & Deploy Shannon / 🏗️ Build & Deploy Shannon (push) Successful in 3m1s
All checks were successful
Build & Deploy Shannon / 🏗️ Build & Deploy Shannon (push) Successful in 3m1s
- Implemented bandwidth sensitivity and power sensitivity plots. - Created a contour map for bit rate multiplying factors. - Added input parameters for C/N and bandwidth with validation. - Displayed computed results and sensitivity analysis metrics. - Integrated interactive graphs for user exploration. - Included background information section for user guidance.
This commit is contained in:
917
views/satellite_types.py
Normal file
917
views/satellite_types.py
Normal file
@@ -0,0 +1,917 @@
|
||||
"""
|
||||
Satellite Types — Mission Control Dashboard
|
||||
=============================================
|
||||
Each satellite category has its own unique animated scene:
|
||||
- Navigation → GPS triangulation on a world map
|
||||
- Communication → Data-flow network between ground stations
|
||||
- Earth Observation → Top-down terrain scan with image strips
|
||||
- Weather → Atmospheric cloud/rain/temperature visualisation
|
||||
- Science → Deep-space telescope view (nebula zoom)
|
||||
- Defense → Tactical radar sweep with blips
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import streamlit.components.v1 as components
|
||||
|
||||
|
||||
_SATTYPES_HTML = r"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{background:transparent;overflow:hidden;font-family:'Segoe UI',system-ui,sans-serif;color:#e2e8f0}
|
||||
|
||||
/* ── Tab bar ── */
|
||||
#tabs{
|
||||
position:absolute;top:0;left:0;right:0;height:52px;
|
||||
display:flex;align-items:center;justify-content:center;gap:6px;
|
||||
background:rgba(10,18,32,0.92);border-bottom:1px solid rgba(79,195,247,0.15);
|
||||
backdrop-filter:blur(10px);z-index:10;padding:0 10px;
|
||||
}
|
||||
.tab{
|
||||
display:flex;align-items:center;gap:6px;
|
||||
padding:8px 14px;border-radius:10px;cursor:pointer;
|
||||
font-size:12px;font-weight:600;letter-spacing:0.3px;
|
||||
border:1px solid transparent;transition:all 0.3s;
|
||||
color:#94a3b8;white-space:nowrap;
|
||||
}
|
||||
.tab:hover{background:rgba(79,195,247,0.06);color:#cbd5e1}
|
||||
.tab.active{
|
||||
background:rgba(79,195,247,0.1);
|
||||
border-color:rgba(79,195,247,0.3);color:#e2e8f0;
|
||||
}
|
||||
.tab .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||||
|
||||
/* ── Info panel ── */
|
||||
#info{
|
||||
position:absolute;top:64px;right:14px;width:290px;
|
||||
background:rgba(10,18,32,0.94);border:1px solid rgba(79,195,247,0.2);
|
||||
border-radius:14px;padding:18px 20px;
|
||||
backdrop-filter:blur(14px);box-shadow:0 8px 32px rgba(0,0,0,0.5);
|
||||
font-size:12px;line-height:1.7;z-index:5;
|
||||
transition:opacity 0.4s;
|
||||
}
|
||||
#info h3{color:#4FC3F7;font-size:13px;margin-bottom:10px;text-transform:uppercase;letter-spacing:1px;text-align:center}
|
||||
.row{display:flex;justify-content:space-between;padding:2px 0}
|
||||
.row .lbl{color:#64748b}.row .val{color:#cbd5e1;font-weight:600;text-align:right;max-width:160px}
|
||||
.sep{border:none;border-top:1px solid rgba(79,195,247,0.1);margin:7px 0}
|
||||
.tag{display:inline-block;background:rgba(79,195,247,0.08);border:1px solid rgba(79,195,247,0.18);
|
||||
border-radius:6px;padding:1px 7px;margin:2px;font-size:10.5px;color:#4FC3F7}
|
||||
.fact{background:rgba(79,195,247,0.05);border-radius:8px;padding:8px 10px;margin-top:6px}
|
||||
.fact b{color:#4FC3F7;font-size:11px}
|
||||
.fact p{color:#cbd5e1;font-size:11px;margin-top:3px;line-height:1.5}
|
||||
|
||||
/* ── Scene label ── */
|
||||
#sceneLabel{
|
||||
position:absolute;bottom:14px;left:50%;transform:translateX(-50%);
|
||||
background:rgba(10,18,32,0.85);border:1px solid rgba(79,195,247,0.15);
|
||||
border-radius:10px;padding:8px 18px;font-size:11px;color:#94a3b8;
|
||||
backdrop-filter:blur(8px);text-align:center;z-index:5;
|
||||
pointer-events:none;transition:opacity 0.3s;
|
||||
}
|
||||
#sceneLabel span{color:#e2e8f0;font-weight:700}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
|
||||
<div id="tabs"></div>
|
||||
<div id="info"><h3>🛰️ Satellite Missions</h3><p style="color:#64748b;text-align:center;padding:12px 0">Select a mission type above<br>to explore its animation.</p></div>
|
||||
<div id="sceneLabel"><span>Mission Control</span> — choose a category</div>
|
||||
|
||||
<script>
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// DATA
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
const cats=[
|
||||
{id:'nav',icon:'🧭',name:'Navigation',color:'#34d399',rgb:'52,211,153',
|
||||
orbit:'MEO',alt:'20 200 km',band:'L-band (1.2–1.6 GHz)',power:'~50 W/signal',
|
||||
precision:'< 1 m (dual-freq)',lifetime:'12–15 yrs',
|
||||
examples:['GPS (USA)','Galileo (EU)','GLONASS (RU)','BeiDou (CN)'],
|
||||
fact:'Each GPS satellite carries 4 atomic clocks accurate to ~1 ns. Relativity corrections of 38 μs/day are applied.',
|
||||
sceneHint:'Triangulation — 3 satellites fix your position'},
|
||||
{id:'com',icon:'📡',name:'Communication',color:'#4FC3F7',rgb:'79,195,247',
|
||||
orbit:'GEO + LEO',alt:'550–36 000 km',band:'C / Ku / Ka-band',power:'2–20 kW',
|
||||
precision:'100+ Gbps (HTS)',lifetime:'15–20 yrs',
|
||||
examples:['Starlink','Intelsat','SES','OneWeb'],
|
||||
fact:'A modern HTS can deliver 500+ Gbps — equivalent to 100 000 HD streams simultaneously.',
|
||||
sceneHint:'Data flowing between ground stations'},
|
||||
{id:'eo',icon:'📸',name:'Earth Observation',color:'#a78bfa',rgb:'167,139,250',
|
||||
orbit:'LEO (SSO)',alt:'500–800 km',band:'X-band (downlink)',power:'SAR + optical',
|
||||
precision:'0.3–30 m resolution',lifetime:'5–7 yrs',
|
||||
examples:['Sentinel','Landsat','Planet Doves','Pléiades'],
|
||||
fact:'Planet Labs\' 200+ Doves image the entire land surface every single day at 3 m resolution.',
|
||||
sceneHint:'Satellite scanning the terrain below'},
|
||||
{id:'wx',icon:'🌦️',name:'Weather',color:'#fbbf24',rgb:'251,191,36',
|
||||
orbit:'GEO + LEO',alt:'800–36 000 km',band:'L / S / Ka-band',power:'Imager + Sounder',
|
||||
precision:'Full disk every 10 min',lifetime:'10–15 yrs',
|
||||
examples:['Meteosat','GOES','Himawari','FengYun'],
|
||||
fact:'GOES-16 produces 3.6 TB of weather imagery per day across 16 spectral bands.',
|
||||
sceneHint:'Atmospheric monitoring & cloud tracking'},
|
||||
{id:'sci',icon:'🔭',name:'Science',color:'#f472b6',rgb:'244,114,182',
|
||||
orbit:'Various (L2, LEO, HEO)',alt:'540 km – 1.5M km',band:'S / X / Ka-band',power:'Telescope + Spectrometer',
|
||||
precision:'28 Mbps (JWST)',lifetime:'5–20+ yrs',
|
||||
examples:['JWST','Hubble','Gaia','SOHO','Chandra'],
|
||||
fact:'JWST\'s 6.5 m gold mirror operates at −233 °C to see infrared light from the first galaxies, 13.5 billion years ago.',
|
||||
sceneHint:'Deep-space telescope view'},
|
||||
{id:'def',icon:'🛡️',name:'Defense',color:'#fb923c',rgb:'251,146,60',
|
||||
orbit:'LEO / GEO / HEO',alt:'Variable',band:'UHF / SHF / EHF',power:'Anti-jam, encrypted',
|
||||
precision:'SIGINT + IMINT',lifetime:'7–15 yrs',
|
||||
examples:['SBIRS','WGS','Syracuse (FR)','Skynet (UK)'],
|
||||
fact:'SBIRS infrared satellites detect missile launches within seconds from 36 000 km away by spotting the heat plume.',
|
||||
sceneHint:'Tactical radar sweep'},
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CANVAS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
const cv=document.getElementById('c'),cx=cv.getContext('2d');
|
||||
let W,H;
|
||||
function resize(){W=cv.width=window.innerWidth;H=cv.height=window.innerHeight}
|
||||
window.addEventListener('resize',resize);resize();
|
||||
|
||||
let activeCat=null,t=0;
|
||||
|
||||
// Stars
|
||||
const stars=Array.from({length:140},()=>({x:Math.random(),y:Math.random(),r:Math.random()*1+0.3,ph:Math.random()*6.28,sp:0.4+Math.random()*1.2}));
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// BUILD TABS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
const tabsEl=document.getElementById('tabs');
|
||||
cats.forEach(c=>{
|
||||
const d=document.createElement('div');
|
||||
d.className='tab';d.dataset.id=c.id;
|
||||
d.innerHTML='<span class="dot" style="background:'+c.color+'"></span>'+c.icon+' '+c.name;
|
||||
d.onclick=()=>{
|
||||
activeCat=activeCat===c.id?null:c.id;
|
||||
document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('active',t.dataset.id===activeCat));
|
||||
if(activeCat)showInfo(c);else resetInfo();
|
||||
updateLabel();
|
||||
};
|
||||
tabsEl.appendChild(d);
|
||||
});
|
||||
|
||||
function resetInfo(){
|
||||
document.getElementById('info').innerHTML='<h3>🛰️ Satellite Missions</h3><p style="color:#64748b;text-align:center;padding:12px 0">Select a mission type above<br>to explore its animation.</p>';
|
||||
}
|
||||
function showInfo(c){
|
||||
document.getElementById('info').innerHTML=
|
||||
'<h3>'+c.icon+' '+c.name+'</h3>'+
|
||||
'<div class="row"><span class="lbl">Orbit</span><span class="val">'+c.orbit+'</span></div>'+
|
||||
'<div class="row"><span class="lbl">Altitude</span><span class="val">'+c.alt+'</span></div>'+
|
||||
'<div class="row"><span class="lbl">Band</span><span class="val">'+c.band+'</span></div>'+
|
||||
'<div class="row"><span class="lbl">Power / Payload</span><span class="val">'+c.power+'</span></div>'+
|
||||
'<div class="row"><span class="lbl">Performance</span><span class="val">'+c.precision+'</span></div>'+
|
||||
'<div class="row"><span class="lbl">Lifetime</span><span class="val">'+c.lifetime+'</span></div>'+
|
||||
'<hr class="sep">'+
|
||||
'<div style="color:#64748b;font-size:10.5px;margin-bottom:4px">Notable systems:</div>'+
|
||||
'<div>'+c.examples.map(function(e){return '<span class="tag">'+e+'</span>'}).join('')+'</div>'+
|
||||
'<hr class="sep">'+
|
||||
'<div class="fact"><b>💡 Did you know?</b><p>'+c.fact+'</p></div>';
|
||||
}
|
||||
function updateLabel(){
|
||||
var el=document.getElementById('sceneLabel');
|
||||
if(!activeCat){el.innerHTML='<span>Mission Control</span> — choose a category';return}
|
||||
var c=cats.find(function(x){return x.id===activeCat});
|
||||
el.innerHTML='<span>'+c.icon+' '+c.name+'</span> — '+c.sceneHint;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE HELPERS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var sceneX=0, sceneY=52;
|
||||
function sceneW(){return W-310}
|
||||
function sceneH(){return H-52}
|
||||
|
||||
function drawBg(){
|
||||
var g=cx.createLinearGradient(0,0,0,H);
|
||||
g.addColorStop(0,'#060d19');g.addColorStop(1,'#0a1628');
|
||||
cx.fillStyle=g;cx.fillRect(0,0,W,H);
|
||||
stars.forEach(function(s){
|
||||
var a=0.2+Math.sin(t*s.sp+s.ph)*0.3+0.3;
|
||||
cx.beginPath();cx.arc(s.x*W,s.y*H,s.r,0,6.28);
|
||||
cx.fillStyle='rgba(255,255,255,'+a+')';cx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE: NAVIGATION — GPS triangulation
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var navReceiverPath=[];
|
||||
for(var ni=0;ni<200;ni++){
|
||||
navReceiverPath.push({fx:0.35+Math.sin(ni*0.04)*0.18, fy:0.7+Math.cos(ni*0.06)*0.1});
|
||||
}
|
||||
var navIdx=0;
|
||||
|
||||
function drawNav(){
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var col='52,211,153';
|
||||
|
||||
// Ground / horizon
|
||||
cx.fillStyle='rgba(10,30,20,0.5)';
|
||||
cx.fillRect(ox,oy+sh*0.65,sw,sh*0.35);
|
||||
cx.strokeStyle='rgba(52,211,153,0.15)';cx.lineWidth=1;
|
||||
cx.beginPath();cx.moveTo(ox,oy+sh*0.65);cx.lineTo(ox+sw,oy+sh*0.65);cx.stroke();
|
||||
|
||||
// Grid on ground
|
||||
cx.strokeStyle='rgba(52,211,153,0.06)';
|
||||
for(var gi=0;gi<20;gi++){
|
||||
var gx=ox+(sw/20)*gi;
|
||||
cx.beginPath();cx.moveTo(gx,oy+sh*0.65);cx.lineTo(gx,oy+sh);cx.stroke();
|
||||
}
|
||||
for(var gj=0;gj<6;gj++){
|
||||
var gy=oy+sh*0.65+((sh*0.35)/6)*gj;
|
||||
cx.beginPath();cx.moveTo(ox,gy);cx.lineTo(ox+sw,gy);cx.stroke();
|
||||
}
|
||||
|
||||
// 3 satellites positions
|
||||
var sats=[
|
||||
{x:ox+sw*0.18,y:oy+sh*0.1},{x:ox+sw*0.52,y:oy+sh*0.06},{x:ox+sw*0.82,y:oy+sh*0.15}
|
||||
];
|
||||
|
||||
// Receiver
|
||||
navIdx=(navIdx+0.3)%navReceiverPath.length;
|
||||
var rp=navReceiverPath[Math.floor(navIdx)];
|
||||
var rx=ox+sw*rp.fx,ry=oy+sh*rp.fy;
|
||||
|
||||
// Draw range circles from each sat
|
||||
sats.forEach(function(s,i){
|
||||
var dist=Math.sqrt((s.x-rx)*(s.x-rx)+(s.y-ry)*(s.y-ry));
|
||||
// Fixed-speed expanding circles (maxR = constant, not dist-dependent)
|
||||
var maxR=280;
|
||||
var speed=35;
|
||||
for(var r=0;r<3;r++){
|
||||
var cr=((t*speed+i*93+r*(maxR/3))%maxR);
|
||||
var alpha=cr<dist ? 0.22*(1-cr/maxR) : 0.06*(1-cr/maxR);
|
||||
cx.beginPath();cx.arc(s.x,s.y,cr,0,6.28);
|
||||
cx.strokeStyle='rgba('+col+','+alpha+')';cx.lineWidth=1.5;cx.stroke();
|
||||
}
|
||||
|
||||
var lineAlpha=0.08+Math.sin(t*2+i)*0.05;
|
||||
cx.beginPath();cx.moveTo(s.x,s.y);cx.lineTo(rx,ry);
|
||||
cx.strokeStyle='rgba('+col+','+lineAlpha+')';cx.lineWidth=0.8;
|
||||
cx.setLineDash([4,6]);cx.stroke();cx.setLineDash([]);
|
||||
|
||||
cx.save();
|
||||
cx.shadowColor='rgba('+col+',0.6)';cx.shadowBlur=12;
|
||||
cx.beginPath();cx.arc(s.x,s.y,7,0,6.28);
|
||||
cx.fillStyle='rgba('+col+',0.9)';cx.fill();
|
||||
cx.restore();
|
||||
cx.strokeStyle='rgba('+col+',0.5)';cx.lineWidth=2;
|
||||
cx.beginPath();cx.moveTo(s.x-12,s.y);cx.lineTo(s.x+12,s.y);cx.stroke();
|
||||
cx.fillStyle='rgba('+col+',0.7)';cx.font='bold 10px system-ui';cx.textAlign='center';
|
||||
cx.fillText('SV'+(i+1),s.x,s.y-14);
|
||||
});
|
||||
|
||||
// Receiver
|
||||
cx.save();
|
||||
cx.shadowColor='rgba(52,211,153,0.8)';cx.shadowBlur=16;
|
||||
cx.beginPath();cx.arc(rx,ry,6,0,6.28);
|
||||
cx.fillStyle='#34d399';cx.fill();
|
||||
cx.restore();
|
||||
cx.strokeStyle='rgba(52,211,153,0.6)';cx.lineWidth=1;
|
||||
cx.beginPath();cx.moveTo(rx-12,ry);cx.lineTo(rx+12,ry);cx.moveTo(rx,ry-12);cx.lineTo(rx,ry+12);cx.stroke();
|
||||
cx.beginPath();cx.arc(rx,ry,10,0,6.28);cx.stroke();
|
||||
cx.fillStyle='#34d399';cx.font='bold 10px monospace';cx.textAlign='center';
|
||||
var lat=(rp.fy*90-20).toFixed(2),lon=(rp.fx*360-90).toFixed(2);
|
||||
cx.fillText(lat+'°N '+lon+'°E',rx,ry+24);
|
||||
cx.font='9px system-ui';cx.fillStyle='rgba(52,211,153,0.6)';
|
||||
cx.fillText('Fix: 3D — Accuracy: 0.8 m',rx,ry+36);
|
||||
cx.textAlign='start';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE: COMMUNICATION — Network data flow
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var comNodes=[];
|
||||
var comLinks=[];
|
||||
var comPackets=[];
|
||||
var comInit=false;
|
||||
|
||||
function initCom(){
|
||||
if(comInit)return;comInit=true;
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var positions=[
|
||||
{fx:0.08,fy:0.4,label:'New York'},{fx:0.25,fy:0.25,label:'London'},
|
||||
{fx:0.38,fy:0.35,label:'Paris'},{fx:0.55,fy:0.22,label:'Moscow'},
|
||||
{fx:0.7,fy:0.45,label:'Dubai'},{fx:0.82,fy:0.3,label:'Tokyo'},
|
||||
{fx:0.6,fy:0.7,label:'Mumbai'},{fx:0.2,fy:0.65,label:'São Paulo'},
|
||||
{fx:0.45,fy:0.6,label:'Nairobi'},{fx:0.9,fy:0.6,label:'Sydney'},
|
||||
];
|
||||
positions.forEach(function(p){
|
||||
comNodes.push({x:ox+sw*p.fx,y:oy+sh*p.fy,label:p.label,pulse:Math.random()*6.28});
|
||||
});
|
||||
var pairs=[[0,1],[1,2],[2,3],[3,5],[4,5],[4,6],[6,8],[8,7],[7,0],[1,4],[2,8],[5,9],[6,9],[0,7],[3,4]];
|
||||
pairs.forEach(function(pr){comLinks.push({a:pr[0],b:pr[1]})});
|
||||
for(var pi=0;pi<20;pi++){
|
||||
var l=comLinks[Math.floor(Math.random()*comLinks.length)];
|
||||
comPackets.push({link:l,progress:Math.random(),speed:0.003+Math.random()*0.004,forward:Math.random()>0.5});
|
||||
}
|
||||
}
|
||||
|
||||
function drawCom(){
|
||||
initCom();
|
||||
var col='79,195,247';
|
||||
|
||||
comLinks.forEach(function(l){
|
||||
var a=comNodes[l.a],b=comNodes[l.b];
|
||||
var mx=(a.x+b.x)/2,my=(a.y+b.y)/2-30;
|
||||
cx.beginPath();cx.moveTo(a.x,a.y);cx.quadraticCurveTo(mx,my,b.x,b.y);
|
||||
cx.strokeStyle='rgba('+col+',0.1)';cx.lineWidth=1;cx.stroke();
|
||||
});
|
||||
|
||||
comPackets.forEach(function(p){
|
||||
p.progress+=p.speed*(p.forward?1:-1);
|
||||
if(p.progress>1||p.progress<0){
|
||||
p.forward=!p.forward;
|
||||
p.progress=Math.max(0,Math.min(1,p.progress));
|
||||
}
|
||||
var a=comNodes[p.link.a],b=comNodes[p.link.b];
|
||||
var mx=(a.x+b.x)/2,my=(a.y+b.y)/2-30;
|
||||
var tt=p.progress;
|
||||
var px=(1-tt)*(1-tt)*a.x+2*(1-tt)*tt*mx+tt*tt*b.x;
|
||||
var py=(1-tt)*(1-tt)*a.y+2*(1-tt)*tt*my+tt*tt*b.y;
|
||||
cx.save();
|
||||
cx.shadowColor='rgba('+col+',0.7)';cx.shadowBlur=6;
|
||||
cx.beginPath();cx.arc(px,py,2.5,0,6.28);
|
||||
cx.fillStyle='rgba('+col+',0.85)';cx.fill();
|
||||
cx.restore();
|
||||
});
|
||||
|
||||
comNodes.forEach(function(n){
|
||||
var pr=8+Math.sin(t*2+n.pulse)*3;
|
||||
cx.beginPath();cx.arc(n.x,n.y,pr,0,6.28);
|
||||
cx.strokeStyle='rgba('+col+',0.15)';cx.lineWidth=1;cx.stroke();
|
||||
cx.save();
|
||||
cx.shadowColor='rgba('+col+',0.5)';cx.shadowBlur=10;
|
||||
cx.beginPath();cx.arc(n.x,n.y,5,0,6.28);
|
||||
cx.fillStyle='rgba('+col+',0.9)';cx.fill();
|
||||
cx.restore();
|
||||
cx.fillStyle='rgba('+col+',0.65)';cx.font='9px system-ui';cx.textAlign='center';
|
||||
cx.fillText(n.label,n.x,n.y+18);
|
||||
});
|
||||
|
||||
cx.fillStyle='rgba(79,195,247,0.4)';cx.font='11px monospace';cx.textAlign='start';
|
||||
var tp=Math.floor(280+Math.sin(t)*40);
|
||||
cx.fillText('Aggregate throughput: '+tp+' Gbps',sceneX+16,sceneY+sceneH()-20);
|
||||
cx.textAlign='start';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE: EARTH OBSERVATION — Scanning terrain
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
function drawEO(){
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var col='167,139,250';
|
||||
|
||||
// Terrain
|
||||
var groundY=oy+sh*0.7;
|
||||
cx.fillStyle='rgba(30,20,50,0.5)';
|
||||
cx.fillRect(ox,groundY,sw,sh*0.3);
|
||||
|
||||
cx.beginPath();cx.moveTo(ox,groundY);
|
||||
for(var x=0;x<sw;x+=4){
|
||||
var h=Math.sin(x*0.01)*15+Math.sin(x*0.035+2)*8+Math.cos(x*0.007)*12;
|
||||
cx.lineTo(ox+x,groundY-h);
|
||||
}
|
||||
cx.lineTo(ox+sw,groundY+10);cx.lineTo(ox,groundY+10);cx.closePath();
|
||||
cx.fillStyle='rgba(60,40,90,0.4)';cx.fill();
|
||||
cx.strokeStyle='rgba('+col+',0.2)';cx.lineWidth=1;cx.stroke();
|
||||
|
||||
cx.strokeStyle='rgba('+col+',0.04)';
|
||||
for(var gx=0;gx<sw;gx+=30){cx.beginPath();cx.moveTo(ox+gx,groundY-20);cx.lineTo(ox+gx,oy+sh);cx.stroke();}
|
||||
for(var gy=groundY;gy<oy+sh;gy+=20){cx.beginPath();cx.moveTo(ox,gy);cx.lineTo(ox+sw,gy);cx.stroke();}
|
||||
|
||||
var satX=ox+((t*25)%sw);
|
||||
var satY=oy+sh*0.12;
|
||||
|
||||
// Scan beam
|
||||
var beamW=40;
|
||||
cx.beginPath();
|
||||
cx.moveTo(satX,satY+8);
|
||||
cx.lineTo(satX-beamW,groundY-15);
|
||||
cx.lineTo(satX+beamW,groundY-15);
|
||||
cx.closePath();
|
||||
var beamG=cx.createLinearGradient(satX,satY,satX,groundY);
|
||||
beamG.addColorStop(0,'rgba('+col+',0.25)');
|
||||
beamG.addColorStop(1,'rgba('+col+',0.02)');
|
||||
cx.fillStyle=beamG;cx.fill();
|
||||
|
||||
cx.beginPath();
|
||||
cx.moveTo(satX-beamW,groundY-15);
|
||||
cx.lineTo(satX+beamW,groundY-15);
|
||||
cx.strokeStyle='rgba('+col+',0.7)';cx.lineWidth=2;cx.stroke();
|
||||
|
||||
// Image strips
|
||||
var stripY=oy+sh*0.45;
|
||||
var stripH=sh*0.18;
|
||||
var scannedW=satX-ox-beamW;
|
||||
if(scannedW>0){
|
||||
var bands=['rgba(80,200,120,0.15)','rgba(200,80,80,0.12)','rgba(80,80,200,0.12)'];
|
||||
bands.forEach(function(b,i){
|
||||
cx.fillStyle=b;
|
||||
cx.fillRect(ox,stripY+i*(stripH/3),scannedW,stripH/3);
|
||||
});
|
||||
cx.strokeStyle='rgba('+col+',0.2)';cx.lineWidth=0.5;
|
||||
cx.strokeRect(ox,stripY,scannedW,stripH);
|
||||
cx.font='9px monospace';cx.textAlign='start';
|
||||
['NIR','RED','BLUE'].forEach(function(b,i){
|
||||
cx.fillStyle='rgba('+col+',0.5)';
|
||||
cx.fillText(b,ox+4,stripY+i*(stripH/3)+12);
|
||||
});
|
||||
}
|
||||
|
||||
// Satellite body
|
||||
cx.save();
|
||||
cx.shadowColor='rgba('+col+',0.7)';cx.shadowBlur=14;
|
||||
cx.fillStyle='rgba('+col+',0.9)';
|
||||
cx.fillRect(satX-6,satY-4,12,8);
|
||||
cx.restore();
|
||||
cx.fillStyle='rgba('+col+',0.4)';
|
||||
cx.fillRect(satX-22,satY-2,14,4);
|
||||
cx.fillRect(satX+8,satY-2,14,4);
|
||||
cx.beginPath();cx.arc(satX,satY+5,3,0,6.28);
|
||||
cx.fillStyle='rgba('+col+',0.8)';cx.fill();
|
||||
|
||||
cx.fillStyle='rgba('+col+',0.6)';cx.font='bold 10px system-ui';cx.textAlign='center';
|
||||
cx.fillText('Sentinel-2A — SSO 786 km',satX,satY-14);
|
||||
|
||||
cx.fillStyle='rgba('+col+',0.4)';cx.font='10px monospace';cx.textAlign='start';
|
||||
cx.fillText('GSD: 10 m | Swath: 290 km | Bands: 13',ox+16,oy+sh-20);
|
||||
cx.textAlign='start';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE: WEATHER — Atmospheric monitoring
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var wxClouds=Array.from({length:18},function(){return{
|
||||
x:Math.random(),y:0.3+Math.random()*0.4,
|
||||
r:20+Math.random()*40,sp:0.1+Math.random()*0.3,
|
||||
opacity:0.15+Math.random()*0.2
|
||||
}});
|
||||
var wxRaindrops=Array.from({length:60},function(){return{
|
||||
x:Math.random(),y:Math.random(),sp:2+Math.random()*3,len:4+Math.random()*8
|
||||
}});
|
||||
|
||||
function drawWx(){
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var col='251,191,36';
|
||||
|
||||
var tg=cx.createLinearGradient(ox,oy,ox,oy+sh);
|
||||
tg.addColorStop(0,'rgba(30,10,60,0.3)');
|
||||
tg.addColorStop(0.4,'rgba(10,20,50,0.3)');
|
||||
tg.addColorStop(1,'rgba(10,30,40,0.3)');
|
||||
cx.fillStyle=tg;cx.fillRect(ox,oy,sw,sh);
|
||||
|
||||
// Isobars
|
||||
cx.strokeStyle='rgba(251,191,36,0.06)';cx.lineWidth=1;
|
||||
for(var ii=0;ii<8;ii++){
|
||||
cx.beginPath();
|
||||
var baseY=oy+sh*0.2+ii*(sh*0.08);
|
||||
cx.moveTo(ox,baseY);
|
||||
for(var ix=0;ix<sw;ix+=5){
|
||||
var iy=baseY+Math.sin((ix+t*20)*0.008+ii)*15;
|
||||
cx.lineTo(ox+ix,iy);
|
||||
}
|
||||
cx.stroke();
|
||||
if(ii%2===0){
|
||||
cx.fillStyle='rgba(251,191,36,0.15)';cx.font='8px monospace';
|
||||
cx.fillText((1013-ii*4)+'hPa',ox+sw-60,baseY-3);
|
||||
}
|
||||
}
|
||||
|
||||
// Clouds
|
||||
wxClouds.forEach(function(c){
|
||||
c.x+=c.sp*0.0003;
|
||||
if(c.x>1.15)c.x=-0.15;
|
||||
var cloudX=ox+sw*c.x,cloudY=oy+sh*c.y;
|
||||
cx.fillStyle='rgba(200,210,230,'+c.opacity+')';
|
||||
for(var ci=0;ci<4;ci++){
|
||||
cx.beginPath();
|
||||
cx.arc(cloudX+ci*c.r*0.4-c.r*0.6, cloudY+Math.sin(ci*1.5)*c.r*0.15, c.r*0.4,0,6.28);
|
||||
cx.fill();
|
||||
}
|
||||
});
|
||||
|
||||
// Rain
|
||||
cx.strokeStyle='rgba(100,180,255,0.3)';cx.lineWidth=1;
|
||||
wxRaindrops.forEach(function(d){
|
||||
d.y+=d.sp*0.003;
|
||||
if(d.y>1)d.y=0.4;
|
||||
if(d.y>0.5){
|
||||
var dx=ox+sw*d.x,dy=oy+sh*d.y;
|
||||
cx.beginPath();cx.moveTo(dx,dy);cx.lineTo(dx-1,dy+d.len);cx.stroke();
|
||||
}
|
||||
});
|
||||
|
||||
// GEO satellite
|
||||
var satX=ox+sw*0.5,satY=oy+30;
|
||||
cx.save();cx.shadowColor='rgba('+col+',0.6)';cx.shadowBlur=10;
|
||||
cx.beginPath();cx.arc(satX,satY,6,0,6.28);
|
||||
cx.fillStyle='rgba('+col+',0.9)';cx.fill();cx.restore();
|
||||
cx.strokeStyle='rgba('+col+',0.4)';cx.lineWidth=2;
|
||||
cx.beginPath();cx.moveTo(satX-14,satY);cx.lineTo(satX+14,satY);cx.stroke();
|
||||
|
||||
// Scan swath
|
||||
cx.beginPath();
|
||||
cx.moveTo(satX,satY+8);
|
||||
cx.lineTo(ox+sw*0.1,oy+sh*0.9);
|
||||
cx.lineTo(ox+sw*0.9,oy+sh*0.9);
|
||||
cx.closePath();
|
||||
var sg=cx.createLinearGradient(satX,satY,satX,oy+sh);
|
||||
sg.addColorStop(0,'rgba('+col+',0.08)');sg.addColorStop(1,'rgba('+col+',0.01)');
|
||||
cx.fillStyle=sg;cx.fill();
|
||||
|
||||
// Rotating scan line
|
||||
var scanAngle=(t*0.5)%(Math.PI*2);
|
||||
var scanEndX=satX+Math.sin(scanAngle)*sw*0.4;
|
||||
var scanEndY=satY+Math.abs(Math.cos(scanAngle))*sh*0.85;
|
||||
cx.beginPath();cx.moveTo(satX,satY);cx.lineTo(scanEndX,scanEndY);
|
||||
cx.strokeStyle='rgba('+col+',0.3)';cx.lineWidth=1.5;cx.stroke();
|
||||
|
||||
// Temperature scale
|
||||
cx.fillStyle='rgba('+col+',0.5)';cx.font='9px monospace';cx.textAlign='start';
|
||||
var temps=['-60°C','-30°C','0°C','+20°C','+35°C'];
|
||||
temps.forEach(function(tmp,i){
|
||||
var ty=oy+sh*0.15+i*(sh*0.17);
|
||||
cx.fillText(tmp,ox+8,ty);
|
||||
});
|
||||
|
||||
cx.fillStyle='rgba('+col+',0.6)';cx.font='bold 10px system-ui';cx.textAlign='center';
|
||||
cx.fillText('Meteosat — GEO 36 000 km — Full Disk Imaging',satX,satY-12);
|
||||
cx.textAlign='start';
|
||||
cx.fillStyle='rgba('+col+',0.35)';cx.font='10px monospace';
|
||||
cx.fillText('Update: 10 min | 16 bands | 3.6 TB/day',ox+16,oy+sh-20);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE: SCIENCE — Deep space telescope view
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var sciStars=Array.from({length:300},function(){return{
|
||||
x:Math.random(),y:Math.random(),
|
||||
r:Math.random()*1.6+0.2,
|
||||
hue:Math.random()*60+200,
|
||||
br:0.3+Math.random()*0.7,
|
||||
twinkle:Math.random()*6.28
|
||||
}});
|
||||
var sciNebula=Array.from({length:12},function(){return{
|
||||
x:0.3+Math.random()*0.4,y:0.3+Math.random()*0.4,
|
||||
r:40+Math.random()*80,
|
||||
hue:Math.random()*360,
|
||||
alpha:0.03+Math.random()*0.04
|
||||
}});
|
||||
|
||||
function drawSci(){
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var col='244,114,182';
|
||||
|
||||
cx.fillStyle='rgba(3,3,12,0.6)';cx.fillRect(ox,oy,sw,sh);
|
||||
|
||||
sciNebula.forEach(function(n){
|
||||
var nx=ox+sw*n.x,ny=oy+sh*n.y;
|
||||
var grad=cx.createRadialGradient(nx,ny,0,nx,ny,n.r);
|
||||
grad.addColorStop(0,'hsla('+n.hue+',70%,50%,'+(n.alpha+Math.sin(t*0.5+n.hue)*0.01)+')');
|
||||
grad.addColorStop(1,'transparent');
|
||||
cx.fillStyle=grad;
|
||||
cx.fillRect(nx-n.r,ny-n.r,n.r*2,n.r*2);
|
||||
});
|
||||
|
||||
sciStars.forEach(function(s){
|
||||
var alpha=s.br*(0.5+Math.sin(t*1.5+s.twinkle)*0.5);
|
||||
cx.beginPath();
|
||||
cx.arc(ox+sw*s.x,oy+sh*s.y,s.r,0,6.28);
|
||||
cx.fillStyle='hsla('+s.hue+',60%,80%,'+alpha+')';
|
||||
cx.fill();
|
||||
});
|
||||
|
||||
// Telescope reticle
|
||||
var rX=ox+sw*0.42,rY=oy+sh*0.45;
|
||||
var rR=sh*0.28;
|
||||
cx.strokeStyle='rgba('+col+',0.15)';cx.lineWidth=0.8;
|
||||
cx.beginPath();cx.arc(rX,rY,rR,0,6.28);cx.stroke();
|
||||
cx.beginPath();cx.arc(rX,rY,rR*0.6,0,6.28);cx.stroke();
|
||||
cx.beginPath();cx.moveTo(rX-rR,rY);cx.lineTo(rX+rR,rY);cx.stroke();
|
||||
cx.beginPath();cx.moveTo(rX,rY-rR);cx.lineTo(rX,rY+rR);cx.stroke();
|
||||
[0,Math.PI/2,Math.PI,3*Math.PI/2].forEach(function(a){
|
||||
var ix=rX+Math.cos(a)*rR,iy=rY+Math.sin(a)*rR;
|
||||
cx.beginPath();
|
||||
cx.moveTo(ix,iy);cx.lineTo(ix+Math.cos(a)*10,iy+Math.sin(a)*10);
|
||||
cx.strokeStyle='rgba('+col+',0.4)';cx.lineWidth=2;cx.stroke();
|
||||
});
|
||||
|
||||
// Galaxy in reticle
|
||||
var gpulse=0.7+Math.sin(t*1.2)*0.3;
|
||||
var galGrad=cx.createRadialGradient(rX,rY,0,rX,rY,28*gpulse);
|
||||
galGrad.addColorStop(0,'rgba(255,200,100,'+(0.3*gpulse)+')');
|
||||
galGrad.addColorStop(0.4,'rgba('+col+','+(0.15*gpulse)+')');
|
||||
galGrad.addColorStop(1,'transparent');
|
||||
cx.fillStyle=galGrad;
|
||||
cx.fillRect(rX-40,rY-40,80,80);
|
||||
// Spiral arms
|
||||
cx.save();cx.translate(rX,rY);cx.rotate(t*0.1);
|
||||
cx.strokeStyle='rgba(255,200,140,'+(0.1*gpulse)+')';cx.lineWidth=2;
|
||||
cx.beginPath();
|
||||
for(var sa=0;sa<Math.PI*3;sa+=0.1){
|
||||
var sr=3+sa*5;
|
||||
cx.lineTo(Math.cos(sa)*sr,Math.sin(sa)*sr);
|
||||
}
|
||||
cx.stroke();
|
||||
cx.beginPath();
|
||||
for(var sa2=0;sa2<Math.PI*3;sa2+=0.1){
|
||||
var sr2=3+sa2*5;
|
||||
cx.lineTo(-Math.cos(sa2)*sr2,-Math.sin(sa2)*sr2);
|
||||
}
|
||||
cx.stroke();
|
||||
cx.restore();
|
||||
|
||||
// Data readout
|
||||
cx.fillStyle='rgba('+col+',0.5)';cx.font='9px monospace';cx.textAlign='start';
|
||||
var lines=[
|
||||
'Target: SMACS 0723 — z = 7.66',
|
||||
'RA: 07h 23m 19.5s Dec: -73° 27\' 15"',
|
||||
'Exposure: NIRCam F200W — 12.5 hrs',
|
||||
'Signal: '+(14+Math.sin(t)*2).toFixed(1)+' e\u207B/s/px',
|
||||
];
|
||||
lines.forEach(function(l,i){
|
||||
cx.fillText(l,ox+16,oy+sh-60+i*14);
|
||||
});
|
||||
|
||||
cx.fillStyle='rgba('+col+',0.6)';cx.font='bold 10px system-ui';cx.textAlign='center';
|
||||
cx.fillText('JWST — NIRCam Deep Field — L2 Lagrange Point',ox+sw*0.42,oy+20);
|
||||
cx.textAlign='start';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SCENE: DEFENSE — Tactical radar sweep
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var defBlips=Array.from({length:12},function(){return{
|
||||
angle:Math.random()*6.28,
|
||||
dist:0.2+Math.random()*0.7,
|
||||
type:['friendly','hostile','unknown'][Math.floor(Math.random()*3)],
|
||||
label:['F-16','MiG-29','UAV','Ship','Sub','SAM','AWACS','Tanker','C2','Helo','Drone','Cargo'][Math.floor(Math.random()*12)],
|
||||
blinkPhase:Math.random()*6.28
|
||||
}});
|
||||
|
||||
function drawDef(){
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var col='251,146,60';
|
||||
var rcx2=ox+sw*0.42,rcy2=oy+sh*0.52;
|
||||
var rr=Math.min(sw,sh)*0.38;
|
||||
|
||||
cx.fillStyle='rgba(5,15,5,0.4)';
|
||||
cx.beginPath();cx.arc(rcx2,rcy2,rr+10,0,6.28);cx.fill();
|
||||
|
||||
for(var ri=1;ri<=4;ri++){
|
||||
cx.beginPath();cx.arc(rcx2,rcy2,rr*ri/4,0,6.28);
|
||||
cx.strokeStyle='rgba('+col+','+(0.08+ri*0.02)+')';cx.lineWidth=0.8;cx.stroke();
|
||||
cx.fillStyle='rgba('+col+',0.3)';cx.font='8px monospace';
|
||||
cx.fillText(ri*100+'km',rcx2+rr*ri/4+4,rcy2-3);
|
||||
}
|
||||
|
||||
cx.strokeStyle='rgba('+col+',0.07)';cx.lineWidth=0.5;
|
||||
cx.beginPath();cx.moveTo(rcx2-rr,rcy2);cx.lineTo(rcx2+rr,rcy2);cx.stroke();
|
||||
cx.beginPath();cx.moveTo(rcx2,rcy2-rr);cx.lineTo(rcx2,rcy2+rr);cx.stroke();
|
||||
cx.beginPath();cx.moveTo(rcx2-rr*0.707,rcy2-rr*0.707);cx.lineTo(rcx2+rr*0.707,rcy2+rr*0.707);cx.stroke();
|
||||
cx.beginPath();cx.moveTo(rcx2+rr*0.707,rcy2-rr*0.707);cx.lineTo(rcx2-rr*0.707,rcy2+rr*0.707);cx.stroke();
|
||||
|
||||
// Sweep
|
||||
var sweepAngle=(t*0.8)%(Math.PI*2);
|
||||
cx.save();
|
||||
cx.translate(rcx2,rcy2);
|
||||
cx.rotate(sweepAngle);
|
||||
for(var si=0;si<40;si++){
|
||||
var sa=-si*0.02;
|
||||
var salpha=0.25*(1-si/40);
|
||||
cx.beginPath();
|
||||
cx.moveTo(0,0);
|
||||
cx.lineTo(Math.cos(sa)*rr,Math.sin(sa)*rr);
|
||||
cx.strokeStyle='rgba('+col+','+salpha+')';cx.lineWidth=1;cx.stroke();
|
||||
}
|
||||
cx.beginPath();cx.moveTo(0,0);cx.lineTo(rr,0);
|
||||
cx.strokeStyle='rgba('+col+',0.7)';cx.lineWidth=2;cx.stroke();
|
||||
cx.restore();
|
||||
|
||||
// Blips
|
||||
defBlips.forEach(function(b){
|
||||
var bx=rcx2+Math.cos(b.angle)*b.dist*rr;
|
||||
var by=rcy2+Math.sin(b.angle)*b.dist*rr;
|
||||
var angleDiff=(sweepAngle-b.angle+Math.PI*4)%(Math.PI*2);
|
||||
var vis=angleDiff<Math.PI*1.5 ? Math.max(0,1-angleDiff/(Math.PI*1.5)) : 0;
|
||||
if(vis<0.05)return;
|
||||
var colors={friendly:'rgba(52,211,153,',hostile:'rgba(239,68,68,',unknown:'rgba(251,191,36,'};
|
||||
var bc=colors[b.type];
|
||||
cx.save();
|
||||
cx.globalAlpha=vis;
|
||||
cx.shadowColor=bc+'0.8)';cx.shadowBlur=8;
|
||||
cx.beginPath();cx.arc(bx,by,3.5,0,6.28);
|
||||
cx.fillStyle=bc+'0.9)';cx.fill();
|
||||
cx.restore();
|
||||
if(vis>0.4){
|
||||
cx.globalAlpha=vis*0.7;
|
||||
cx.fillStyle=bc+'0.7)';cx.font='8px monospace';cx.textAlign='center';
|
||||
cx.fillText(b.label,bx,by-10);
|
||||
cx.globalAlpha=1;
|
||||
}
|
||||
});
|
||||
cx.globalAlpha=1;
|
||||
|
||||
// Legend
|
||||
cx.font='9px system-ui';cx.textAlign='start';
|
||||
var legendY=oy+sh-55;
|
||||
[['● Friendly','rgba(52,211,153,0.7)'],['● Hostile','rgba(239,68,68,0.7)'],['● Unknown','rgba(251,191,36,0.7)']].forEach(function(item,i){
|
||||
cx.fillStyle=item[1];cx.fillText(item[0],ox+16,legendY+i*14);
|
||||
});
|
||||
|
||||
cx.fillStyle='rgba('+col+',0.5)';cx.font='bold 10px system-ui';cx.textAlign='center';
|
||||
cx.fillText('SBIRS / EW — Surveillance & Early Warning',rcx2,oy+20);
|
||||
cx.font='9px monospace';cx.fillStyle='rgba('+col+',0.35)';
|
||||
cx.fillText('Sweep rate: 7.6 RPM | Range: 400 km | Tracks: '+defBlips.length,rcx2,oy+sh-20);
|
||||
cx.textAlign='start';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// IDLE SCENE — no category selected
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
function drawIdle(){
|
||||
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
|
||||
var cx0=ox+sw*0.42,cy0=oy+sh*0.5;
|
||||
var hexR=Math.min(sw,sh)*0.22;
|
||||
|
||||
cats.forEach(function(ic,i){
|
||||
var angle=i*(Math.PI*2/cats.length)-Math.PI/2+t*0.15;
|
||||
var ix=cx0+Math.cos(angle)*hexR;
|
||||
var iy=cy0+Math.sin(angle)*hexR+Math.sin(t*1.5+i)*8;
|
||||
|
||||
cx.beginPath();cx.arc(cx0,cy0,hexR,0,6.28);
|
||||
cx.strokeStyle='rgba(79,195,247,0.04)';cx.lineWidth=0.8;cx.stroke();
|
||||
|
||||
cx.save();
|
||||
cx.shadowColor='rgba('+ic.rgb+',0.4)';cx.shadowBlur=18;
|
||||
cx.font='28px system-ui';cx.textAlign='center';cx.textBaseline='middle';
|
||||
cx.fillText(ic.icon,ix,iy);
|
||||
cx.restore();
|
||||
|
||||
cx.fillStyle=ic.color;cx.font='bold 10px system-ui';cx.textAlign='center';cx.textBaseline='top';
|
||||
cx.fillText(ic.name,ix,iy+22);
|
||||
cx.textBaseline='alphabetic';
|
||||
});
|
||||
|
||||
cx.fillStyle='#64748b';cx.font='13px system-ui';cx.textAlign='center';
|
||||
cx.fillText('Select a mission type to explore',cx0,cy0+4);
|
||||
cx.textAlign='start';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MAIN LOOP
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
var scenes={nav:drawNav,com:drawCom,eo:drawEO,wx:drawWx,sci:drawSci,def:drawDef};
|
||||
|
||||
function frame(){
|
||||
t+=0.016;
|
||||
cx.clearRect(0,0,W,H);
|
||||
drawBg();
|
||||
|
||||
if(activeCat && scenes[activeCat]){
|
||||
scenes[activeCat]();
|
||||
} else {
|
||||
drawIdle();
|
||||
}
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
frame();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def render():
|
||||
"""Render the satellite missions / types dashboard page."""
|
||||
st.markdown("## 🛰️ Satellite Missions — Types & Applications")
|
||||
st.markdown(
|
||||
"Explore **six categories** of satellite missions through unique animated scenes. "
|
||||
"Click a tab to switch between navigation, communication, observation, weather, "
|
||||
"science and defense — each with its own visual story."
|
||||
)
|
||||
st.divider()
|
||||
|
||||
components.html(_SATTYPES_HTML, height=720, scrolling=False)
|
||||
|
||||
# ── Educational content below ──
|
||||
st.divider()
|
||||
st.markdown("### 📋 Mission Comparison")
|
||||
|
||||
st.markdown("""
|
||||
| Category | Orbit | Altitude | Key Band | Examples |
|
||||
|:---|:---:|:---:|:---:|:---|
|
||||
| 🧭 **Navigation** | MEO | 20 200 km | L-band | GPS, Galileo, GLONASS, BeiDou |
|
||||
| 📡 **Communication** | GEO / LEO | 550 – 36 000 km | C / Ku / Ka | Starlink, Intelsat, SES |
|
||||
| 📸 **Earth Observation** | LEO (SSO) | 500 – 800 km | X-band | Sentinel, Landsat, Planet |
|
||||
| 🌦️ **Weather** | GEO / LEO | 800 – 36 000 km | L / Ka | Meteosat, GOES, Himawari |
|
||||
| 🔭 **Science** | Various | Variable | S / X / Ka | JWST, Hubble, Gaia |
|
||||
| 🛡️ **Defense** | LEO / GEO / HEO | Variable | UHF / EHF | SBIRS, WGS, Syracuse |
|
||||
""")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
with st.expander("🧭 Navigation Satellites"):
|
||||
st.markdown("""
|
||||
**How GNSS works:**
|
||||
|
||||
Each satellite broadcasts its precise position and exact time
|
||||
(from onboard atomic clocks). A receiver picks up
|
||||
signals from $\\geq 4$ satellites and solves the position equations:
|
||||
|
||||
$$d_i = c \\cdot (t_{\\text{rx}} - t_{\\text{tx},i})$$
|
||||
|
||||
Where $d_i$ is the pseudorange to satellite $i$, $c$ is the speed of
|
||||
light, and $t_{\\text{rx}}$ is the receiver clock time.
|
||||
|
||||
With 4+ satellites the receiver solves for $(x, y, z, \\Delta t)$ —
|
||||
3D position plus its own clock error.
|
||||
|
||||
**Key systems:**
|
||||
- **GPS** (USA) — 31 sats, L1 / L2 / L5
|
||||
- **Galileo** (EU) — 30 sats, E1 / E5a / E5b / E6
|
||||
- **GLONASS** (Russia) — 24 sats
|
||||
- **BeiDou** (China) — 35+ sats
|
||||
""")
|
||||
|
||||
with st.expander("📡 Communication Satellites"):
|
||||
st.markdown("""
|
||||
**Evolution:**
|
||||
|
||||
1. **1960s** — Early Bird: 240 voice channels
|
||||
2. **1980s** — Large GEO: thousands of transponders
|
||||
3. **2000s** — HTS: spot beams, frequency reuse
|
||||
4. **2020s** — Mega-constellations (Starlink 6 000+ LEO)
|
||||
|
||||
**Shannon connection:**
|
||||
|
||||
$$C = B \\cdot \\log_2\\!\\left(1 + \\frac{C}{N}\\right)$$
|
||||
|
||||
A GEO satellite at 36 000 km suffers ~50 dB more path loss than
|
||||
LEO at 550 km, but compensates with larger antennas, higher power
|
||||
and advanced modulation (DVB-S2X, 256-APSK).
|
||||
""")
|
||||
|
||||
with st.expander("🌦️ Weather Satellites"):
|
||||
st.markdown("""
|
||||
**Two approaches:**
|
||||
|
||||
1. **GEO** (Meteosat, GOES, Himawari) — continuous monitoring,
|
||||
full disk every 10–15 min, 16+ spectral bands.
|
||||
2. **LEO polar** (MetOp, NOAA) — higher resolution but 2 passes/day,
|
||||
microwave sounders penetrate clouds.
|
||||
|
||||
**Data volume:** GOES-16 generates ~3.6 TB/day of imagery.
|
||||
""")
|
||||
|
||||
with col2:
|
||||
with st.expander("📸 Earth Observation Satellites"):
|
||||
st.markdown("""
|
||||
**Imaging technologies:**
|
||||
|
||||
| Type | Resolution | Use |
|
||||
|:---|:---:|:---|
|
||||
| **Optical** | 0.3 – 1 m | Urban mapping, defence |
|
||||
| **Multispectral** | 5 – 30 m | Agriculture (NDVI) |
|
||||
| **Hyperspectral** | 30 m, 200+ bands | Mineral detection |
|
||||
| **SAR** | 1 – 10 m | All-weather imaging |
|
||||
| **InSAR** | mm displacement | Ground subsidence |
|
||||
|
||||
**Sun-Synchronous Orbit (SSO):** ~98° inclination ensures
|
||||
consistent solar illumination for temporal comparison.
|
||||
""")
|
||||
|
||||
with st.expander("🔭 Science & Exploration"):
|
||||
st.markdown("""
|
||||
| Mission | Location | Purpose |
|
||||
|:---|:---:|:---|
|
||||
| **JWST** | L2 (1.5 M km) | IR astronomy |
|
||||
| **Hubble** | LEO (540 km) | Optical / UV |
|
||||
| **Gaia** | L2 | 3D map of 1.8 B stars |
|
||||
| **Chandra** | HEO | X-ray astronomy |
|
||||
|
||||
**Deep space challenge:** Voyager 1 at 24 billion km transmits
|
||||
at ~160 **bits**/s with 23 W through NASA's 70 m DSN antennas.
|
||||
""")
|
||||
|
||||
with st.expander("🛡️ Defense & Intelligence"):
|
||||
st.markdown("""
|
||||
**Categories:**
|
||||
|
||||
- **MILCOM** — WGS, AEHF, Syracuse (secure links, EHF)
|
||||
- **SIGINT** — intercept electromagnetic emissions
|
||||
- **IMINT** — high-res optical/radar reconnaissance
|
||||
- **Early Warning** — SBIRS IR missile detection from GEO
|
||||
- **ELINT** — radar system characterisation
|
||||
|
||||
**Resilience:** frequency hopping, spread spectrum,
|
||||
radiation hardening, satellite crosslinks.
|
||||
""")
|
||||
Reference in New Issue
Block a user