workspace: initial clean commit

This commit is contained in:
Stream
2026-04-12 21:55:33 +03:00
commit 0fc86ac3ae
220 changed files with 31657 additions and 0 deletions

227
decode.py Normal file
View File

@@ -0,0 +1,227 @@
import subprocess, json, time, threading
from http.server import HTTPServer, BaseHTTPRequestHandler
import pyModeS as pms
aircraft, cpr = {}, {}
HTML = """<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADS-B Радар</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@10/ol.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #1a1a2e; color: #e0e0e0; font-family: 'Courier New', monospace; height: 100vh; overflow: hidden; }
#map { width: 100vw; height: 100vh; }
#header {
position: absolute; top: 0; left: 0; right: 0; z-index: 1000;
background: rgba(26,26,46,0.92); border-bottom: 1px solid #2a2a5a;
padding: 8px 16px; display: flex; align-items: center; gap: 16px;
}
#header h1 { font-size: 16px; color: #7eb8f7; letter-spacing: 2px; }
#count { font-size: 13px; color: #aaa; }
#popup {
position: absolute; z-index: 2000;
background: rgba(16,16,36,0.97); border: 1px solid #3a3a7a;
border-radius: 8px; padding: 12px 16px; min-width: 200px;
box-shadow: 0 4px 24px rgba(0,0,0,0.6);
display: none; pointer-events: none;
}
#popup .icao { font-size: 18px; font-weight: bold; color: #7eb8f7; margin-bottom: 6px; }
#popup .row { font-size: 13px; color: #ccc; margin: 2px 0; }
#popup .label { color: #888; margin-right: 6px; }
.ol-attribution { display: none !important; }
</style>
</head>
<body>
<div id="header">
<h1>✈ ADS-B РАДАР</h1>
<span id="count">Самолётов: 0</span>
</div>
<div id="map"></div>
<div id="popup">
<div class="icao" id="p-icao"></div>
<div class="row"><span class="label">Позывной:</span><span id="p-flight">—</span></div>
<div class="row"><span class="label">Высота:</span><span id="p-alt">—</span></div>
<div class="row"><span class="label">Скорость:</span><span id="p-speed">—</span></div>
<div class="row"><span class="label">Курс:</span><span id="p-track">—</span></div>
<div class="row"><span class="label">Пакетов:</span><span id="p-msgs">—</span></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/ol@10/dist/ol.js"></script>
<script>
const map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
})
})
],
view: new ol.View({ center: ol.proj.fromLonLat([37.62, 55.75]), zoom: 8 }),
controls: []
});
const vectorSource = new ol.source.Vector();
map.addLayer(new ol.layer.Vector({ source: vectorSource }));
const features = {};
let selectedIcao = null;
let nowTs = Date.now() / 1000;
const popup = document.getElementById('popup');
function freshColor(seen) {
const age = nowTs - (seen || 0);
if (age < 15) return '#00ff88';
if (age < 45) return '#ffcc00';
return '#ff4444';
}
function makeStyle(track, selected, seen) {
const color = selected ? '#ffd700' : freshColor(seen);
return new ol.style.Style({ text: new ol.style.Text({
text: '', font: (selected ? 'bold 26px' : '22px') + ' sans-serif',
rotation: (track || 0) * Math.PI / 180,
fill: new ol.style.Fill({ color }),
stroke: new ol.style.Stroke({ color: '#1a1a2e', width: 2 })
})});
}
function showPopup(ac, pixel) {
selectedIcao = ac.icao;
document.getElementById('p-icao').textContent = ac.icao.toUpperCase();
document.getElementById('p-flight').textContent = ac.flight || '';
document.getElementById('p-alt').textContent = ac.altitude != null ? ac.altitude + ' ft' : '';
document.getElementById('p-speed').textContent = ac.speed != null ? ac.speed + ' кт' : '';
document.getElementById('p-track').textContent = ac.track != null ? ac.track + '°' : '';
document.getElementById('p-msgs').textContent = ac.msgs || '';
let [x, y] = pixel;
if (x + 220 > map.getSize()[0]) x -= 230; else x += 20;
popup.style.left = x + 'px'; popup.style.top = (y - 10) + 'px';
popup.style.display = 'block';
if (features[ac.icao]) features[ac.icao].setStyle(makeStyle(ac.track, true, ac.seen));
}
function hidePopup() {
if (selectedIcao && features[selectedIcao]) {
const d = features[selectedIcao]._data;
features[selectedIcao].setStyle(makeStyle(d?.track, false, d?.seen));
}
selectedIcao = null;
popup.style.display = 'none';
}
map.on('click', e => {
const hit = map.forEachFeatureAtPixel(e.pixel, f => f, { hitTolerance: 12 });
hit?._data ? showPopup(hit._data, e.pixel) : hidePopup();
});
map.on('pointermove', e => {
map.getTargetElement().style.cursor =
map.hasFeatureAtPixel(e.pixel, { hitTolerance: 12 }) ? 'pointer' : '';
});
async function refresh() {
try {
const data = await (await fetch('/aircraft.json')).json();
nowTs = data.now;
const active = new Set();
for (const ac of data.aircraft) {
if (ac.lat == null || ac.lon == null) continue;
active.add(ac.icao);
const coord = ol.proj.fromLonLat([ac.lon, ac.lat]);
const sel = ac.icao === selectedIcao;
if (features[ac.icao]) {
features[ac.icao].getGeometry().setCoordinates(coord);
features[ac.icao].setStyle(makeStyle(ac.track, sel, ac.seen));
features[ac.icao]._data = ac;
} else {
const f = new ol.Feature({ geometry: new ol.geom.Point(coord) });
f.setStyle(makeStyle(ac.track, false, ac.seen)); f._data = ac;
vectorSource.addFeature(f); features[ac.icao] = f;
}
}
for (const icao of Object.keys(features)) {
if (!active.has(icao)) { vectorSource.removeFeature(features[icao]); delete features[icao]; }
}
document.getElementById('count').textContent = 'Самолётов: ' + active.size;
} catch(e) {}
}
refresh(); setInterval(refresh, 3000);
</script>
</body>
</html>"""
def decode():
proc = subprocess.Popen(["rtl_adsb.exe"], stdout=subprocess.PIPE, text=True)
for line in proc.stdout:
line = line.strip()
if not (line.startswith('*') and line.endswith(';')):
continue
msg = line[1:-1]
if len(msg) not in (14, 28):
continue
try:
if pms.df(msg) != 17:
continue
icao = pms.icao(msg)
tc = pms.typecode(msg)
if icao not in aircraft:
aircraft[icao] = {'icao': icao}
aircraft[icao]['seen'] = time.time()
aircraft[icao]['msgs'] = aircraft[icao].get('msgs', 0) + 1
if 1 <= tc <= 4:
cs = pms.adsb.callsign(msg).strip()
if cs: aircraft[icao]['flight'] = cs
elif 9 <= tc <= 18:
alt = pms.adsb.altitude(msg)
if alt: aircraft[icao]['altitude'] = alt
oe = pms.adsb.oe_flag(msg)
cpr.setdefault(icao, {})[oe] = (msg, time.time())
try:
pos = pms.adsb.position_with_ref(msg, 55.75, 37.62)
if pos: aircraft[icao]['lat'], aircraft[icao]['lon'] = pos
except: pass
if 0 in cpr[icao] and 1 in cpr[icao]:
m0,t0 = cpr[icao][0]; m1,t1 = cpr[icao][1]
if abs(t0-t1) < 10:
try:
pos = pms.adsb.position(m0, m1, t0, t1)
if pos: aircraft[icao]['lat'], aircraft[icao]['lon'] = pos
except: pass
elif tc == 19:
v = pms.adsb.velocity(msg)
if v: aircraft[icao]['speed'], aircraft[icao]['track'] = v[0], v[1]
except: pass
class H(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/aircraft.json':
now = time.time()
active = [v for v in aircraft.values() if now - v.get('seen',0) < 120]
body = json.dumps({'now': now, 'aircraft': active}).encode()
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(body)
elif self.path == '/':
body = HTML.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
def log_message(self, *a): pass
threading.Thread(target=decode, daemon=True).start()
print("Запущен!")
print(" Карта: http://localhost:8080/")
print(" API: http://localhost:8080/aircraft.json")
HTTPServer(('0.0.0.0', 8080), H).serve_forever()