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

- 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:
Poidevin, Antoine (ITOP CM) - AF
2026-02-20 10:33:09 +01:00
parent beda405953
commit 6a4ccc3376
38 changed files with 4319 additions and 11161 deletions

917
views/satellite_types.py Normal file
View 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.21.6 GHz)',power:'~50 W/signal',
precision:'< 1 m (dual-freq)',lifetime:'1215 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:'55036 000 km',band:'C / Ku / Ka-band',power:'220 kW',
precision:'100+ Gbps (HTS)',lifetime:'1520 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:'500800 km',band:'X-band (downlink)',power:'SAR + optical',
precision:'0.330 m resolution',lifetime:'57 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:'80036 000 km',band:'L / S / Ka-band',power:'Imager + Sounder',
precision:'Full disk every 10 min',lifetime:'1015 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:'520+ 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:'715 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 1015 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.
""")