forked from gsn/predictor
feat: cleanup
This commit is contained in:
parent
8e9f117799
commit
82ef1cb3b8
66 changed files with 0 additions and 9521 deletions
|
|
@ -1,114 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
Datetime string `json:"datetime"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Altitude float64 `json:"altitude"`
|
||||
}
|
||||
|
||||
type Stage struct {
|
||||
Stage string `json:"stage"`
|
||||
Trajectory []Point `json:"trajectory"`
|
||||
}
|
||||
|
||||
type Prediction struct {
|
||||
Prediction []Stage `json:"prediction"`
|
||||
}
|
||||
|
||||
func haversine(lat1, lon1, lat2, lon2 float64) float64 {
|
||||
R := 6371000.0
|
||||
phi1, phi2 := lat1*math.Pi/180, lat2*math.Pi/180
|
||||
dphi := (lat2 - lat1) * math.Pi / 180
|
||||
dlam := (lon2 - lon1) * math.Pi / 180
|
||||
a := math.Sin(dphi/2)*math.Sin(dphi/2) + math.Cos(phi1)*math.Cos(phi2)*math.Sin(dlam/2)*math.Sin(dlam/2)
|
||||
return R * 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||
}
|
||||
|
||||
func load(path string) Prediction {
|
||||
data, _ := os.ReadFile(path)
|
||||
var p Prediction
|
||||
json.Unmarshal(data, &p)
|
||||
return p
|
||||
}
|
||||
|
||||
func main() {
|
||||
our := load("c:/tmp/our.json")
|
||||
taw := load("c:/tmp/tawhiri.json")
|
||||
|
||||
// Find burst and landing points
|
||||
var ourBurst, ourLand, tawBurst, tawLand Point
|
||||
for _, s := range our.Prediction {
|
||||
t := s.Trajectory
|
||||
if s.Stage == "ascent" {
|
||||
ourBurst = t[len(t)-1]
|
||||
}
|
||||
if s.Stage == "descent" {
|
||||
ourLand = t[len(t)-1]
|
||||
}
|
||||
}
|
||||
for _, s := range taw.Prediction {
|
||||
t := s.Trajectory
|
||||
if s.Stage == "ascent" {
|
||||
tawBurst = t[len(t)-1]
|
||||
}
|
||||
if s.Stage == "descent" {
|
||||
tawLand = t[len(t)-1]
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("=== Burst Point ===")
|
||||
fmt.Printf(" Our: lat=%.4f, lon=%.4f, alt=%.0f, time=%s\n", ourBurst.Latitude, ourBurst.Longitude, ourBurst.Altitude, ourBurst.Datetime)
|
||||
fmt.Printf(" Tawhiri: lat=%.4f, lon=%.4f, alt=%.0f, time=%s\n", tawBurst.Latitude, tawBurst.Longitude, tawBurst.Altitude, tawBurst.Datetime)
|
||||
burstDist := haversine(ourBurst.Latitude, ourBurst.Longitude, tawBurst.Latitude, tawBurst.Longitude)
|
||||
fmt.Printf(" Distance: %.2f km\n", burstDist/1000)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("=== Landing Point ===")
|
||||
fmt.Printf(" Our: lat=%.4f, lon=%.4f, alt=%.0f, time=%s\n", ourLand.Latitude, ourLand.Longitude, ourLand.Altitude, ourLand.Datetime)
|
||||
fmt.Printf(" Tawhiri: lat=%.4f, lon=%.4f, alt=%.0f, time=%s\n", tawLand.Latitude, tawLand.Longitude, tawLand.Altitude, tawLand.Datetime)
|
||||
landDist := haversine(ourLand.Latitude, ourLand.Longitude, tawLand.Latitude, tawLand.Longitude)
|
||||
fmt.Printf(" Distance: %.2f km\n", landDist/1000)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("=== Trajectory Comparison (every 10 min) ===")
|
||||
ourPts := map[string]Point{}
|
||||
tawPts := map[string]Point{}
|
||||
for _, s := range our.Prediction {
|
||||
for _, p := range s.Trajectory {
|
||||
ourPts[p.Datetime] = p
|
||||
}
|
||||
}
|
||||
for _, s := range taw.Prediction {
|
||||
for _, p := range s.Trajectory {
|
||||
tawPts[p.Datetime] = p
|
||||
}
|
||||
}
|
||||
|
||||
// Collect common times
|
||||
var common []string
|
||||
for _, s := range our.Prediction {
|
||||
for _, p := range s.Trajectory {
|
||||
if _, ok := tawPts[p.Datetime]; ok {
|
||||
common = append(common, p.Datetime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, t := range common {
|
||||
if i%10 == 0 {
|
||||
o := ourPts[t]
|
||||
tw := tawPts[t]
|
||||
d := haversine(o.Latitude, o.Longitude, tw.Latitude, tw.Longitude)
|
||||
fmt.Printf(" %s: dist=%.2f km (our: %.3f,%.3f vs taw: %.3f,%.3f)\n",
|
||||
t, d/1000, o.Latitude, o.Longitude, tw.Latitude, tw.Longitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.intra.yksa.space/gsn/predictor/internal/pkg/grib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
cfg := &grib.Config{
|
||||
Dir: "C:/tmp/grib",
|
||||
TTL: 48 * time.Hour,
|
||||
CacheTTL: 1 * time.Hour,
|
||||
Parallel: 8,
|
||||
}
|
||||
|
||||
svc, err := grib.New(cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete old cube to force rebuild
|
||||
cubePath := "C:/tmp/grib/20260212_12.cube"
|
||||
if err := os.Remove(cubePath); err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf("Remove cube error: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Old cube removed")
|
||||
}
|
||||
|
||||
// Update will download missing pgrb2b files and rebuild cube
|
||||
fmt.Println("Starting update (download pgrb2b + rebuild cube)...")
|
||||
start := time.Now()
|
||||
if err := svc.Update(ctx); err != nil {
|
||||
fmt.Printf("Update error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Done in %v\n", time.Since(start))
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.intra.yksa.space/gsn/predictor/internal/pkg/grib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Найти последний доступный прогноз
|
||||
run, err := grib.GetLatestModelRun(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("Error finding model run: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Found model run: %v\n", run)
|
||||
|
||||
// Создать downloader
|
||||
dl := grib.NewPartialDownloader("C:/tmp/grib", 8)
|
||||
|
||||
// Запустить загрузку
|
||||
start := time.Now()
|
||||
fmt.Println("Starting download...")
|
||||
|
||||
err = dl.Run(ctx, run)
|
||||
if err != nil {
|
||||
fmt.Printf("Download error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Download completed in %v\n", time.Since(start))
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
mmap "github.com/edsrzf/mmap-go"
|
||||
)
|
||||
|
||||
var pressureLevels = []float64{
|
||||
1000, 975, 950, 925, 900, 875, 850, 825, 800, 775,
|
||||
750, 725, 700, 675, 650, 625, 600, 575, 550, 525,
|
||||
500, 475, 450, 425, 400, 375, 350, 325, 300, 275,
|
||||
250, 225, 200, 175, 150, 125, 100, 70, 50, 30,
|
||||
20, 10, 7, 5, 3, 2, 1,
|
||||
}
|
||||
|
||||
func main() {
|
||||
f, _ := os.Open("C:/tmp/grib/20260212_12.cube")
|
||||
mm, _ := mmap.Map(f, mmap.RDONLY, 0)
|
||||
defer mm.Unmap()
|
||||
defer f.Close()
|
||||
|
||||
const (
|
||||
nT = 97
|
||||
nP = 47
|
||||
nLat = 721
|
||||
nLon = 1440
|
||||
)
|
||||
bytesPerVar := int64(nT * nP * nLat * nLon * 4)
|
||||
|
||||
val := func(varIdx, ti, pi, y, x int) float32 {
|
||||
idx := (((ti*nP + pi) * nLat) + y) * nLon + x
|
||||
off := int64(varIdx)*bytesPerVar + int64(idx)*4
|
||||
bits := binary.LittleEndian.Uint32(mm[off : off+4])
|
||||
return math.Float32frombits(bits)
|
||||
}
|
||||
|
||||
// Check gh values at lat=52.2N (y=(90-52.2)*4=151.2 → y=151), lon=0.1E (x=0.1*4=0.4 → x=0)
|
||||
// Time step 9 (9 hours into forecast)
|
||||
ti := 9
|
||||
y := 151
|
||||
x := 0
|
||||
|
||||
fmt.Println("GH values at (52.25N, 0E), t=+9h:")
|
||||
fmt.Printf("%8s %8s %10s\n", "Level", "hPa", "GH(m)")
|
||||
for pi := 0; pi < nP; pi++ {
|
||||
gh := val(0, ti, pi, y, x)
|
||||
fmt.Printf("%8d %8.0f %10.1f\n", pi, pressureLevels[pi], gh)
|
||||
}
|
||||
|
||||
fmt.Println("\nU-wind values at same point:")
|
||||
fmt.Printf("%8s %8s %10s\n", "Level", "hPa", "U(m/s)")
|
||||
for pi := 0; pi < nP; pi++ {
|
||||
u := val(1, ti, pi, y, x)
|
||||
fmt.Printf("%8d %8.0f %10.2f\n", pi, pressureLevels[pi], u)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/nilsmagnus/grib/griblib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.Open("C:/tmp/grib/gfs.t18z.pgrb2.0p25.f000")
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
messages, err := griblib.ReadMessages(f)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading GRIB: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d messages\n\n", len(messages))
|
||||
|
||||
for i, m := range messages {
|
||||
product := m.Section4.ProductDefinitionTemplate
|
||||
if product.ParameterCategory != 2 || product.ParameterNumber != 2 {
|
||||
continue // only u-wind
|
||||
}
|
||||
fmt.Printf("UGRD Msg %d: SurfType=%d SurfValue=%d SurfScale=%d DataLen=%d\n",
|
||||
i,
|
||||
product.FirstSurface.Type,
|
||||
product.FirstSurface.Value,
|
||||
product.FirstSurface.Scale,
|
||||
len(m.Data()))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.intra.yksa.space/gsn/predictor/internal/pkg/grib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Инициализируем GRIB сервис
|
||||
cfg := &grib.Config{
|
||||
Dir: "C:/tmp/grib",
|
||||
TTL: 48 * time.Hour,
|
||||
CacheTTL: 1 * time.Hour,
|
||||
Parallel: 8,
|
||||
}
|
||||
|
||||
svc, err := grib.New(cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating service: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем данные (создаёт куб)
|
||||
fmt.Println("Updating GRIB data (building cube)...")
|
||||
start := time.Now()
|
||||
if err := svc.Update(ctx); err != nil {
|
||||
fmt.Printf("Update error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Cube built in %v\n", time.Since(start))
|
||||
|
||||
// Тестируем извлечение ветра
|
||||
fmt.Println("\nTesting wind extraction...")
|
||||
lat, lon, alt := 52.2, 0.1, 10000.0
|
||||
ts := time.Date(2026, 2, 11, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
wind, err := svc.Extract(ctx, lat, lon, alt, ts)
|
||||
if err != nil {
|
||||
fmt.Printf("Extract error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Wind at (%.2f, %.2f, %.0fm) at %v:\n", lat, lon, alt, ts)
|
||||
fmt.Printf(" U (east): %.2f m/s\n", wind[0])
|
||||
fmt.Printf(" V (north): %.2f m/s\n", wind[1])
|
||||
|
||||
// Сравниваем с Tawhiri
|
||||
fmt.Println("\nComparing with Tawhiri API...")
|
||||
tawhiriURL := fmt.Sprintf(
|
||||
"https://api.v2.sondehub.org/tawhiri?launch_latitude=%.2f&launch_longitude=%.2f&launch_altitude=0&launch_datetime=%s&ascent_rate=5&burst_altitude=30000&descent_rate=5",
|
||||
lat, lon, ts.Format(time.RFC3339),
|
||||
)
|
||||
|
||||
resp, err := http.Get(tawhiriURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Tawhiri request error: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var tawhiriResp map[string]interface{}
|
||||
json.Unmarshal(body, &tawhiriResp)
|
||||
|
||||
// Выводим финальную точку приземления
|
||||
if prediction, ok := tawhiriResp["prediction"].([]interface{}); ok {
|
||||
for _, stage := range prediction {
|
||||
stageMap := stage.(map[string]interface{})
|
||||
if stageMap["stage"] == "descent" {
|
||||
trajectory := stageMap["trajectory"].([]interface{})
|
||||
if len(trajectory) > 0 {
|
||||
last := trajectory[len(trajectory)-1].(map[string]interface{})
|
||||
fmt.Printf("\nTawhiri landing point:\n")
|
||||
fmt.Printf(" Lat: %.4f\n", last["latitude"])
|
||||
fmt.Printf(" Lon: %.4f\n", last["longitude"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,303 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
from typing import Any
|
||||
import base64
|
||||
import math
|
||||
|
||||
# --- Config ---
|
||||
REFERENCE_API_URL = "https://fly.stratonautica.ru/api/v2/?profile=standard_profile&pred_type=single&launch_datetime=2025-06-25T20%3A45%3A00Z&launch_latitude=56.6992&launch_longitude=38.8247&launch_altitude=0&ascent_rate=5&burst_altitude=30000&descent_rate=5"
|
||||
LOCAL_API_URL = "http://localhost:8080/api/v1/prediction?profile=standard_profile&pred_type=single&launch_datetime=2025-06-25T20%3A45%3A00Z&launch_latitude=56.6992&launch_longitude=38.8247&launch_altitude=0&ascent_rate=5&burst_altitude=30000&descent_rate=5"
|
||||
|
||||
LOCAL_API_PAYLOAD = {
|
||||
"launch_latitude": 56.6992,
|
||||
"launch_longitude": 38.8247,
|
||||
"launch_datetime": "2025-06-25T20-45-000Z",
|
||||
"launch_altitude": 0,
|
||||
"profile": "standard_profile",
|
||||
"ascent_rate": 5,
|
||||
"burst_altitude": 30000,
|
||||
"descent_rate": 5,
|
||||
"format": "json"
|
||||
}
|
||||
READY_URL = "http://localhost:8080/ready"
|
||||
|
||||
# --- Utility functions ---
|
||||
def run_compose_up():
|
||||
print("[INFO] Running docker-compose down --remove-orphans ...")
|
||||
result = subprocess.run(["docker-compose", "down", "--remove-orphans"], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
print("[ERROR] docker-compose down failed:", result.stderr.decode())
|
||||
sys.exit(1)
|
||||
print("[INFO] docker-compose down completed.")
|
||||
print("[INFO] Running docker-compose up -d ...")
|
||||
result = subprocess.run(["docker-compose", "up", "-d"], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
print("[ERROR] docker-compose up failed:", result.stderr.decode())
|
||||
sys.exit(1)
|
||||
print("[INFO] docker-compose up -d completed.")
|
||||
return True
|
||||
|
||||
def wait_for_ready(timeout=900):
|
||||
print(f"[INFO] Waiting for {READY_URL} to be ready ...")
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
resp = requests.get(READY_URL, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if data.get("status") == "ok":
|
||||
print("[INFO] Service is ready.")
|
||||
return
|
||||
else:
|
||||
print(f"[INFO] Not ready yet: {data}")
|
||||
else:
|
||||
print(f"[INFO] /ready returned status {resp.status_code}")
|
||||
except Exception as e:
|
||||
print(f"[INFO] Exception while polling /ready: {e}")
|
||||
time.sleep(10)
|
||||
print(f"[ERROR] Service did not become ready in {timeout} seconds.")
|
||||
sys.exit(1)
|
||||
|
||||
def fetch_reference():
|
||||
print(f"[INFO] Fetching reference prediction from {REFERENCE_API_URL}")
|
||||
resp = requests.get(REFERENCE_API_URL, timeout=60)
|
||||
if resp.status_code != 200:
|
||||
print(f"[ERROR] Reference API returned {resp.status_code}: {resp.text}")
|
||||
sys.exit(1)
|
||||
return resp.json()
|
||||
|
||||
def fetch_local():
|
||||
print(f"[INFO] Fetching local prediction from {LOCAL_API_URL}")
|
||||
resp = requests.get(LOCAL_API_URL, timeout=60)
|
||||
if resp.status_code != 200:
|
||||
print(f"[ERROR] Local API returned {resp.status_code}: {resp.text}")
|
||||
sys.exit(1)
|
||||
return resp.json()
|
||||
|
||||
def haversine(lat1, lon1, lat2, lon2):
|
||||
"""Calculate the great-circle distance between two points on the Earth (specified in decimal degrees). Returns distance in kilometers."""
|
||||
R = 6371.0 # Earth radius in kilometers
|
||||
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
|
||||
dlat = lat2 - lat1
|
||||
dlon = lon2 - lon1
|
||||
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
|
||||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||||
return R * c
|
||||
|
||||
def compare_results(reference_data, local_data):
|
||||
"""Compare prediction results between reference and local APIs."""
|
||||
print("[INFO] Comparing results ...")
|
||||
|
||||
# Extract trajectory data
|
||||
ref_trajectory = reference_data.get('prediction', [{}])[0].get('trajectory', [])
|
||||
local_trajectory = local_data.get('prediction', [{}])[0].get('trajectory', [])
|
||||
|
||||
print(f"[DEBUG] Reference trajectory length: {len(ref_trajectory)}")
|
||||
print(f"[DEBUG] Local trajectory length: {len(local_trajectory)}")
|
||||
|
||||
# Show first 3 points from both APIs
|
||||
print("\n[DEBUG] First 3 points - Reference API:")
|
||||
for i, point in enumerate(ref_trajectory[:3]):
|
||||
print(f" [{i}] alt={point.get('altitude', 'N/A')}, lat={point.get('latitude', 'N/A')}, lon={point.get('longitude', 'N/A')}, time={point.get('datetime', 'N/A')}")
|
||||
|
||||
print("\n[DEBUG] First 3 points - Local API:")
|
||||
for i, point in enumerate(local_trajectory[:3]):
|
||||
print(f" [{i}] alt={point.get('altitude', 'N/A')}, lat={point.get('latitude', 'N/A')}, lon={point.get('longitude', 'N/A')}, time={point.get('datetime', 'N/A')}")
|
||||
|
||||
# Show last 3 points from both APIs
|
||||
print("\n[DEBUG] Last 3 points - Reference API:")
|
||||
for i, point in enumerate(ref_trajectory[-3:]):
|
||||
idx = len(ref_trajectory) - 3 + i
|
||||
print(f" [{idx}] alt={point.get('altitude', 'N/A')}, lat={point.get('latitude', 'N/A')}, lon={point.get('longitude', 'N/A')}, time={point.get('datetime', 'N/A')}")
|
||||
|
||||
print("\n[DEBUG] Last 3 points - Local API:")
|
||||
for i, point in enumerate(local_trajectory[-3:]):
|
||||
idx = len(local_trajectory) - 3 + i
|
||||
print(f" [{idx}] alt={point.get('altitude', 'N/A')}, lat={point.get('latitude', 'N/A')}, lon={point.get('longitude', 'N/A')}, time={point.get('datetime', 'N/A')}")
|
||||
|
||||
# Compare trajectory lengths
|
||||
if len(ref_trajectory) != len(local_trajectory):
|
||||
print(f"[DIFF] Trajectory length mismatch: {len(local_trajectory)} vs {len(ref_trajectory)}")
|
||||
return False
|
||||
|
||||
# Compare trajectory points and calculate drift
|
||||
min_len = min(len(ref_trajectory), len(local_trajectory))
|
||||
max_drift = 0.0
|
||||
max_drift_idx = -1
|
||||
drift_list = []
|
||||
print("\n[DRIFT] Trajectory point-by-point distance (km):")
|
||||
for i in range(min_len):
|
||||
ref_point = ref_trajectory[i]
|
||||
local_point = local_trajectory[i]
|
||||
ref_lat = ref_point.get('latitude')
|
||||
ref_lon = ref_point.get('longitude')
|
||||
local_lat = local_point.get('latitude')
|
||||
local_lon = local_point.get('longitude')
|
||||
drift_km = None
|
||||
if None not in (ref_lat, ref_lon, local_lat, local_lon):
|
||||
drift_km = haversine(ref_lat, ref_lon, local_lat, local_lon)
|
||||
drift_list.append(drift_km)
|
||||
if drift_km > max_drift:
|
||||
max_drift = drift_km
|
||||
max_drift_idx = i
|
||||
print(f" [{i}] Drift: {drift_km:.3f} km")
|
||||
else:
|
||||
print(f" [{i}] Drift: N/A (missing lat/lon)")
|
||||
if drift_list:
|
||||
mean_drift = sum(drift_list) / len(drift_list)
|
||||
print(f"\n[DRIFT] Max drift: {max_drift:.3f} km at idx {max_drift_idx}")
|
||||
print(f"[DRIFT] Mean drift: {mean_drift:.3f} km over {len(drift_list)} points")
|
||||
else:
|
||||
print("[DRIFT] No valid drift data to report.")
|
||||
# Continue with original comparison for altitude, etc.
|
||||
for i in range(min_len):
|
||||
ref_point = ref_trajectory[i]
|
||||
local_point = local_trajectory[i]
|
||||
for key in ['altitude', 'latitude', 'longitude']:
|
||||
ref_val = ref_point.get(key)
|
||||
local_val = local_point.get(key)
|
||||
if ref_val is not None and local_val is not None:
|
||||
if abs(ref_val - local_val) > 0.1:
|
||||
print(f"[DIFF] At idx {i}, key {key}: {local_val} != {ref_val}")
|
||||
return False
|
||||
print("[SUCCESS] Results match!")
|
||||
return True
|
||||
|
||||
def test_custom_profile():
|
||||
"""Test custom profile with base64 encoded curve."""
|
||||
print("\n[TEST] Testing custom_profile...")
|
||||
# Create a simple custom ascent curve (altitude vs time in seconds)
|
||||
curve_data = {
|
||||
"altitude": [0, 30000],
|
||||
"time": [0, 6000]
|
||||
}
|
||||
curve_b64 = base64.b64encode(json.dumps(curve_data).encode()).decode()
|
||||
# Test parameters for custom profile
|
||||
params = {
|
||||
"launch_latitude": 56.6992,
|
||||
"launch_longitude": 38.8247,
|
||||
"launch_datetime": "2025-06-25T13:28:00Z",
|
||||
"launch_altitude": 0,
|
||||
"profile": "custom_profile",
|
||||
"ascent_curve": curve_b64
|
||||
}
|
||||
try:
|
||||
# Test local API (use GET)
|
||||
local_resp = requests.get(
|
||||
"http://localhost:8080/api/v1/prediction",
|
||||
params=params,
|
||||
timeout=30
|
||||
)
|
||||
local_resp.raise_for_status()
|
||||
local_data = local_resp.json()
|
||||
print(f"[INFO] Custom profile test - Local API returned {len(local_data.get('prediction', [{}])[0].get('trajectory', []))} trajectory points")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Custom profile test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_all_profiles():
|
||||
"""Test all available profiles."""
|
||||
profiles = [
|
||||
("standard_profile", "Standard profile test"),
|
||||
("float_profile", "Float profile test"),
|
||||
("reverse_profile", "Reverse profile test"),
|
||||
("custom_profile", "Custom profile test")
|
||||
]
|
||||
|
||||
results = {}
|
||||
|
||||
for profile, description in profiles:
|
||||
print(f"\n[TEST] {description}...")
|
||||
|
||||
if profile == "custom_profile":
|
||||
success = test_custom_profile()
|
||||
else:
|
||||
success = test_single_profile(profile)
|
||||
|
||||
results[profile] = success
|
||||
print(f"[RESULT] {profile}: {'PASS' if success else 'FAIL'}")
|
||||
|
||||
# Print summary
|
||||
print("\n" + "="*50)
|
||||
print("TEST SUMMARY")
|
||||
print("="*50)
|
||||
for profile, success in results.items():
|
||||
status = "PASS" if success else "FAIL"
|
||||
print(f"{profile:20} : {status}")
|
||||
|
||||
total_tests = len(results)
|
||||
passed_tests = sum(results.values())
|
||||
print(f"\nTotal tests: {total_tests}, Passed: {passed_tests}, Failed: {total_tests - passed_tests}")
|
||||
|
||||
return all(results.values())
|
||||
|
||||
def test_single_profile(profile):
|
||||
"""Test a single profile against reference API."""
|
||||
# Test parameters
|
||||
params = {
|
||||
"launch_latitude": 56.6992,
|
||||
"launch_longitude": 38.8247,
|
||||
"launch_datetime": "2025-06-25T13:28:00Z",
|
||||
"launch_altitude": 0,
|
||||
"profile": profile,
|
||||
"ascent_rate": 5,
|
||||
"burst_altitude": 30000,
|
||||
"descent_rate": 5
|
||||
}
|
||||
# Add float altitude for float profile
|
||||
if profile == "float_profile":
|
||||
params["float_altitude"] = 25000
|
||||
try:
|
||||
# Test local API (use GET)
|
||||
local_resp = requests.get(
|
||||
"http://localhost:8080/api/v1/prediction",
|
||||
params=params,
|
||||
timeout=30
|
||||
)
|
||||
local_resp.raise_for_status()
|
||||
local_data = local_resp.json()
|
||||
print(f"[INFO] {profile} - Local API returned {len(local_data.get('prediction', [{}])[0].get('trajectory', []))} trajectory points")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[ERROR] {profile} test failed: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main test function."""
|
||||
print("[INFO] Starting comprehensive predictor API tests...")
|
||||
|
||||
# Run the original standard profile test
|
||||
print("\n[TEST] Running original standard_profile test...")
|
||||
run_compose_up()
|
||||
wait_for_ready()
|
||||
ref = fetch_reference()
|
||||
local = fetch_local()
|
||||
|
||||
print("[INFO] Comparing results ...")
|
||||
original_success = compare_results(ref, local)
|
||||
|
||||
if original_success:
|
||||
print("[SUCCESS] Original standard_profile test passed!")
|
||||
else:
|
||||
print("[FAIL] Original standard_profile test failed!")
|
||||
|
||||
# Test all profiles
|
||||
print("\n[TEST] Running all profile tests...")
|
||||
all_profiles_success = test_all_profiles()
|
||||
|
||||
# Final result
|
||||
overall_success = original_success and all_profiles_success
|
||||
print(f"\n[FINAL RESULT] Overall: {'PASS' if overall_success else 'FAIL'}")
|
||||
|
||||
if overall_success:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.intra.yksa.space/gsn/predictor/internal/pkg/grib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
cfg := &grib.Config{
|
||||
Dir: "C:/tmp/grib",
|
||||
TTL: 48 * time.Hour,
|
||||
CacheTTL: 1 * time.Hour,
|
||||
Parallel: 8,
|
||||
}
|
||||
|
||||
svc, err := grib.New(cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := svc.Update(ctx); err != nil {
|
||||
fmt.Printf("Update error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test wind at lat=52.2, lon=0.1 at various altitudes
|
||||
// Run is 2026-02-12T12:00Z, request time 21:00Z = +9 hours
|
||||
ts := time.Date(2026, 2, 12, 21, 0, 0, 0, time.UTC)
|
||||
lat, lon := 52.2, 0.1
|
||||
|
||||
fmt.Println("Wind at (52.2°N, 0.1°E) at 2026-02-12T21:00Z:")
|
||||
fmt.Printf("%8s %8s %8s\n", "Alt(m)", "U(m/s)", "V(m/s)")
|
||||
|
||||
for _, alt := range []float64{0, 1000, 3000, 5000, 7000, 10000, 15000, 20000, 25000, 30000} {
|
||||
w, err := svc.Extract(ctx, lat, lon, alt, ts)
|
||||
if err != nil {
|
||||
fmt.Printf("%8.0f Error: %v\n", alt, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%8.0f %8.2f %8.2f\n", alt, w[0], w[1])
|
||||
}
|
||||
|
||||
// Also test at a few nearby points to check spatial consistency
|
||||
fmt.Println("\nWind at 10km altitude, varying longitude:")
|
||||
for _, testLon := range []float64{0.0, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 350.0, 359.75} {
|
||||
w, _ := svc.Extract(ctx, lat, testLon, 10000, ts)
|
||||
fmt.Printf(" lon=%6.2f: U=%8.2f V=%8.2f\n", testLon, w[0], w[1])
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue