No se porque pero no me ha funcionado el enlace del codigo asi que lo pego aqui:
Código:
import math
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
# ---------------- utilidades ----------------
def deg2rad(d): return math.radians(d)
def rad2deg(r): return (math.degrees(r) + 360) % 360
def normalize360(a): return (a + 360) % 360
def signed_diff(a,b): return (a - b + 180) % 360 - 180
def vector_from_speed_dir(speed, direction_deg, dir_type="to"):
if dir_type == "from":
direction_deg = normalize360(direction_deg + 180)
r = deg2rad(direction_deg)
x = speed * math.sin(r)
y = speed * math.cos(r)
return x, y
def cog_from_vector(vx, vy): return normalize360(rad2deg(math.atan2(vx, vy)))
# ---------------- viento real/aparente ----------------
def true_wind_from_apparent(awa_dir, awa_speed, boat_heading, boat_speed, awa_dir_type="from"):
aw_x, aw_y = vector_from_speed_dir(awa_speed, awa_dir, dir_type=awa_dir_type)
b_x, b_y = vector_from_speed_dir(boat_speed, boat_heading, dir_type="to")
tw_x = aw_x + b_x
tw_y = aw_y + b_y
tw_speed = math.hypot(tw_x, tw_y)
tw_dir_to = cog_from_vector(tw_x, tw_y)
tw_dir_from = normalize360(tw_dir_to + 180)
return tw_speed, tw_dir_from, (tw_x, tw_y)
# ---------------- abatimiento ----------------
def calc_abatimiento(tws, twa_signed_deg, boat_speed, K=2.0):
twa_rad = math.radians(abs(twa_signed_deg))
denom = max(boat_speed, 0.1)
return abs(K * (tws * math.sin(twa_rad)) / denom)
# ---------------- COG con abatimiento y corriente ----------------
def compute_COG_with_abat_and_current(heading_deg, boat_speed, tw_speed, tw_dir_from_deg,
K, curr_dir_to_deg, curr_speed):
twa_signed = signed_diff(tw_dir_from_deg, heading_deg)
abat = calc_abatimiento(tws, twa_signed, boat_speed, K)
heading_abat = normalize360(heading_deg - abat if twa_signed > 0 else heading_deg + abat)
bx, by = vector_from_speed_dir(boat_speed, heading_abat, dir_type="to")
cx, cy = vector_from_speed_dir(curr_speed, curr_dir_to_deg, dir_type="to")
gx, gy = bx + cx, by + cy
cog = cog_from_vector(gx, gy)
return heading_abat, bx, by, gx, gy, cog, abat, twa_signed
# ---------------- calcular maniobra ----------------
def compute_angle_of_maneuver(hdg1, hdg2, boat_speed, curr_dir_to_deg, curr_speed,
awa_dir, awa_speed, awa_dir_type, K):
tws, tw_dir_from, _ = true_wind_from_apparent(awa_dir, awa_speed, hdg1, boat_speed, awa_dir_type)
h1_abat, bx1, by1, gx1, gy1, COG1, abat1, twa1_signed = compute_COG_with_abat_and_current(
hdg1, boat_speed, tws, tw_dir_from, K, curr_dir_to_deg, curr_speed)
h2_abat, bx2, by2, gx2, gy2, COG2, abat2, twa2_signed = compute_COG_with_abat_and_current(
hdg2, boat_speed, tws, tw_dir_from, K, curr_dir_to_deg, curr_speed)
delta = abs(COG2 - COG1)
if delta > 180: delta = 360 - delta
details = {
"bx1_by1": (bx1, by1), "bx2_by2": (bx2, by2),
"cx_cy": vector_from_speed_dir(curr_speed, curr_dir_to_deg, dir_type="to"),
"gx1_gy1": (gx1, gy1), "gx2_gy2": (gx2, gy2),
"true_wind_vec_to": vector_from_speed_dir(tws, tw_dir_from+180, dir_type="to"),
"apparent_wind_vec_to": vector_from_speed_dir(awa_speed, awa_dir, dir_type=awa_dir_type),
"COG1": COG1, "COG2": COG2, "angle_real_deg": delta,
"heading1_abat": h1_abat, "heading2_abat": h2_abat,
"abat1": abat1, "abat2": abat2
}
return details
# ---------------- calcular heading para mantener TWA ----------------
def compute_heading_for_target_TWA(target_twa_signed, boat_speed, tws, tw_dir_from_deg,
K, curr_dir_to_deg, curr_speed, tol=0.1, max_iter=100):
heading_low = 0.0
heading_high = 360.0
for _ in range(max_iter):
heading_mid = (heading_low + heading_high)/2
twa_signed = signed_diff(tw_dir_from_deg, heading_mid)
abat = calc_abatimiento(tws, twa_signed, boat_speed, K)
heading_abat = normalize360(heading_mid - abat if twa_signed>0 else heading_mid + abat)
error = twa_signed - target_twa_signed
if abs(error)<tol: return normalize360(heading_mid)
if error>0: heading_high = heading_mid
else: heading_low = heading_mid
return normalize360(heading_mid)
# ---------------- dibujo ----------------
def draw_vectors(ax, details):
ax.clear()
ax.axhline(0, color='0.7'); ax.axvline(0, color='0.7')
def draw(vec,label,color):
x, y = vec
ax.arrow(0,0,x,y,head_width=0.2,length_includes_head=True,color=color)
# Calcular desplazamiento dinámico
angle = math.atan2(y, x)
offset = 0.5 # distancia desde el extremo del vector
dx = offset * math.cos(angle)
dy = offset * math.sin(angle)
ax.annotate(label, xy=(x, y), xytext=(x+dx, y+dy),
fontsize=9, color=color,
arrowprops=dict(arrowstyle="-", color=color, lw=0.5))
# Lista de vectores
for key,color,label in [("bx1_by1",'blue',"Barco agua antes"),
("bx2_by2",'green',"Barco agua despues"),
("cx_cy",'orange',"Corriente"),
("gx1_gy1",'red',"Fondo antes (COG1)"),
("gx2_gy2",'purple',"Fondo despues (COG2)"),
("true_wind_vec_to",'cyan',"Viento real"),
("apparent_wind_vec_to",'brown',"Viento aparente")]:
if key in details: draw(details[key],label,color)
ax.set_aspect('equal'); ax.grid(True)
ax.set_xlabel("Este"); ax.set_ylabel("Norte")
# COGs y ángulo de giro
cog1 = details.get("COG1",0)
cog2 = details.get("COG2",0)
delta = details.get("angle_real_deg",0)
hdg2 = details.get("heading2_abat",0)
ax.set_title(f"COG1: {cog1:.1f}°, COG2: {cog2:.1f}°")
ax.text(1.02, 0.95, f"Ángulo real de giro: {delta:.1f}°\nHDG después: {hdg2:.1f}°",
transform=ax.transAxes, fontsize=10,
verticalalignment='top', bbox=dict(facecolor='white', alpha=0.8))
# ---------------- script interactivo ----------------
plt.rcParams['figure.figsize'] = [10,8]
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25,bottom=0.50)
# Valores iniciales
hdg1 = 45.0; boat_speed = 6.0
curr_dir_to = 90.0; curr_speed = 0.0
awa_dir = 320.0; awa_speed = 15.0
K = 2.0
# Cálculo TWA inicial y HDG2
tws, tw_dir_from, _ = true_wind_from_apparent(awa_dir, awa_speed, hdg1, boat_speed, "from")
twa_signed_init = signed_diff(tw_dir_from, hdg1)
hdg2 = compute_heading_for_target_TWA(twa_signed_init, boat_speed, tws, tw_dir_from, K, curr_dir_to, curr_speed)
details = compute_angle_of_maneuver(hdg1, hdg2, boat_speed, curr_dir_to, curr_speed, awa_dir, awa_speed, "from", K)
draw_vectors(ax, details)
# ---------------- sliders ----------------
axcolor = 'lightgoldenrodyellow'
ax_hdg1 = plt.axes([0.25,0.40,0.65,0.03], facecolor=axcolor)
ax_boat = plt.axes([0.25,0.35,0.65,0.03], facecolor=axcolor)
ax_awa = plt.axes([0.25,0.30,0.65,0.03], facecolor=axcolor)
ax_curr_dir = plt.axes([0.25,0.25,0.65,0.03], facecolor=axcolor)
ax_curr_speed = plt.axes([0.25,0.20,0.65,0.03], facecolor=axcolor)
ax_K = plt.axes([0.25,0.15,0.65,0.03], facecolor=axcolor)
ax_awa_dir = plt.axes([0.25,0.10,0.65,0.03], facecolor=axcolor)
s_hdg1 = Slider(ax_hdg1,'HDG1',0,360,valinit=hdg1)
s_boat = Slider(ax_boat,'Vel Barco',0,20,valinit=boat_speed)
s_awa = Slider(ax_awa,'AWA Speed',0,30,valinit=awa_speed)
s_curr_dir = Slider(ax_curr_dir,'Dir Corriente',0,360,valinit=curr_dir_to)
s_curr_speed = Slider(ax_curr_speed,'Vel Corriente',0,10,valinit=curr_speed)
s_K = Slider(ax_K,'K abat',0,5,valinit=K)
s_awa_dir = Slider(ax_awa_dir,'Dir AWA',0,360,valinit=awa_dir)
def update(val):
hdg1_ = s_hdg1.val
boat_speed_ = s_boat.val
awa_speed_ = s_awa.val
curr_dir_to_ = s_curr_dir.val
curr_speed_ = s_curr_speed.val
K_ = s_K.val
awa_dir_ = s_awa_dir.val
tws_, tw_dir_from_, _ = true_wind_from_apparent(awa_dir_, awa_speed_, hdg1_, boat_speed_, "from")
hdg2_ = compute_heading_for_target_TWA(signed_diff(tw_dir_from_, hdg1_), boat_speed_, tws_, tw_dir_from_, K_, curr_dir_to_, curr_speed_)
details_ = compute_angle_of_maneuver(hdg1_, hdg2_, boat_speed_, curr_dir_to_, curr_speed_, awa_dir_, awa_speed_, "from", K_)
draw_vectors(ax, details_)
s_hdg1.on_changed(update)
s_boat.on_changed(update)
s_awa.on_changed(update)
s_curr_dir.on_changed(update)
s_curr_speed.on_changed(update)
s_K.on_changed(update)
s_awa_dir.on_changed(update)
plt.show()