-
Notifications
You must be signed in to change notification settings - Fork 1
Try to reproduce heading gate results from Bart #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ufechner7
wants to merge
45
commits into
main
Choose a base branch
from
heading
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 23 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
a662556
Add test_heading
ufechner7 a1f2a5b
Fix example
ufechner7 7356082
Cleanup
ufechner7 39b779a
Add requirements
ufechner7 3a40936
Add calc_orientation
ufechner7 52799c4
Cleanup
ufechner7 9384a37
add calc_kite_heading
ufechner7 707bed6
Add plot
ufechner7 18c6182
Add z (height) as parameter
ufechner7 48d061d
Some progress
ufechner7 0d845ba
More progress
ufechner7 d8d40d2
Fix warning
ufechner7 78cc2ff
Fix warning
ufechner7 9eb0498
fix naming
ufechner7 f850755
Fix comment
ufechner7 2285cd1
Fix comment
ufechner7 e8e9183
Fix warnings
ufechner7 c7e0cf3
Some progress
ufechner7 8acfbe6
Some progress
ufechner7 26bf16a
Next iteration
ufechner7 975f7f0
Next try
ufechner7 965cd9c
Next try
ufechner7 41b0f30
Next try
ufechner7 3e2f5a9
Fix comments
ufechner7 8df8eda
Update struct arrays
1-Bart-1 1e89584
Update StructArrays in CHANGELOG for v0.11.2
1-Bart-1 08b571e
Bump version from 0.11.1 to 0.11.2
1-Bart-1 3cda666
Add workspace
ufechner7 0cf9a7d
Add Project.toml to examples folder
ufechner7 f0ffa4a
Merge branch 'main' into heading
ufechner7 48549eb
Add ControlPlots
ufechner7 15d7493
Update example
ufechner7 52a8884
Some progress
ufechner7 c5e34e0
Some progress
ufechner7 e635667
Tether is shown now
ufechner7 761287f
Fly three loops
ufechner7 e88d556
Some corrections
ufechner7 83e2305
Show the heading rate in the GUI
ufechner7 7c9cb3e
Fix the glitch when the heading goes from -180 to 180
ufechner7 ee946e2
Three test cases now
ufechner7 b61f090
Fixes
ufechner7 6702032
Add TODO
ufechner7 ba68460
use 30 degrees
ufechner7 6c7cc93
Merge branch 'main' into heading
ufechner7 0a37a8a
Use asin2
ufechner7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,266 @@ | ||
| # SPDX-FileCopyrightText: 2022 Uwe Fechner | ||
| # SPDX-License-Identifier: MIT | ||
|
|
||
| using ControlPlots, KiteUtils, LinearAlgebra, Rotations | ||
|
|
||
| """ | ||
| calc_circle_basis(x, z) | ||
|
|
||
| Compute the orthonormal basis vectors `(e1, e2)` for the circle plane perpendicular | ||
| to the center line. The center line goes from the origin to `(x, 0, z)` in ENU. | ||
|
|
||
| - `e1`: the "up" direction in the circle plane (component of world-up perpendicular to center line). | ||
| - `e2`: the "right" direction when viewed from the ground station (`cross(d, e1)`). | ||
| """ | ||
| function calc_circle_basis(x, z) | ||
| d = normalize([x, 0.0, z]) # center line direction (ENU) | ||
| up = [0.0, 0.0, 1.0] # world up | ||
| e1_raw = up - dot(up, d) * d # up component ⊥ center line | ||
| e1 = normalize(e1_raw) | ||
| e2 = cross(d, e1) # right when viewed from ground station | ||
| return (e1, e2) | ||
| end | ||
|
|
||
| function calc_theta(x, r) | ||
| # Calculate the angle θ between the center line and the line from the ground station to the kite | ||
| # when the kite is at the top of the circle (turn_angle = 0). | ||
| # This is used to verify that the elevation angle at turn_angle=0 matches θ. | ||
| return atan(r, x) | ||
| end | ||
|
|
||
| function calc_r(x, theta) | ||
| # Calculate the circle radius r from the distance x and the angle θ. | ||
| # Inverse of calc_theta: given θ = atan(r, x), we get r = x * tan(θ). | ||
| return x * tan(theta) | ||
| end | ||
|
|
||
| """ | ||
| calc_elevation_azimuth(turn_angle; x=100.0, z=0.0, r=20.0) | ||
|
|
||
| Calculate the elevation and azimuth angles in radian for a kite flying on a | ||
| circle of radius `r`, centered at `(x, 0, z)` in the ENU reference frame. | ||
| The circle plane is perpendicular to the center line (the line from the origin to | ||
| the center). | ||
|
|
||
| The turn angle is measured from the top of the circle (12 o'clock position), going | ||
| clockwise when viewed from the ground station (looking along the tether). | ||
|
|
||
| # Arguments | ||
| - `turn_angle`: the position of the kite on the circle in radian (0 to 2π). | ||
| 0 means the top of the circle (highest point), π/2 means the kite | ||
| is to the right (south), π means the bottom, 3π/2 means the kite | ||
| is to the left (north). | ||
| - `x`: distance of the circle center from the ground station along the east axis [m]. Default: 100.0. | ||
| - `z`: height of the circle center above the ground [m]. Default: 0.0. | ||
| - `r`: radius of the circle [m]. Default: 20.0. | ||
|
|
||
| # Returns | ||
| A tuple `(elevation, azimuth)` in radian. | ||
| - `elevation`: the elevation angle of the kite as seen from the ground station. | ||
| - `azimuth`: the azimuth angle (east-based) of the kite as seen from the ground station. | ||
| """ | ||
| function calc_elevation_azimuth(turn_angle; x=100.0, z=0.0, r=20.0) | ||
| center = [x, 0.0, z] | ||
| e1, e2 = calc_circle_basis(x, z) | ||
| # Kite position on circle in the plane ⊥ tether | ||
| # turn_angle = 0 → top (e1), π/2 → right (e2), π → bottom, 3π/2 → left | ||
| pos = center + r * cos(turn_angle) * e1 + r * sin(turn_angle) * e2 | ||
| elevation = calc_elevation(pos) | ||
| azimuth = azimuth_east(pos) | ||
| return (elevation, azimuth) | ||
| end | ||
|
|
||
| """ | ||
| calc_orientation(turn_angle; x=100.0, z=0.0, r=20.0) | ||
|
|
||
| Calculate the orientation of the kite as (roll, pitch, yaw) in radian for a given turn angle | ||
| in radian. The kite is assumed to be oriented tangentially to the circle (pointing in the | ||
| direction of motion). | ||
|
|
||
| The circle plane is perpendicular to the center line (from origin to `(x, 0, z)`). | ||
|
|
||
| The kite reference frame (KS) is defined as: | ||
| - x: from trailing edge to leading edge (flight direction) | ||
| - y: to the right looking in flight direction | ||
| - z: down (along the tether toward the ground station) | ||
|
|
||
| The orientation is expressed with respect to the NED reference frame using | ||
| `calc_orient_rot` and `quat2euler` from KiteUtils.jl. | ||
|
|
||
| # Arguments | ||
| - `turn_angle`: the position of the kite on the circle in radian (0 to 2π). | ||
| - `x`: distance of the circle center from the ground station along the east axis [m]. Default: 100.0. | ||
| - `z`: height of the circle center above the ground [m]. Default: 0.0. | ||
| - `r`: radius of the circle [m]. Default: 20.0. | ||
|
|
||
| # Returns | ||
| A tuple `(roll, pitch, yaw)` in radian. | ||
| """ | ||
| function calc_orientation(turn_angle; x=100.0, z=0.0, r=20.0) | ||
| center = [x, 0.0, z] | ||
| e1, e2 = calc_circle_basis(x, z) | ||
|
|
||
| # Kite position on the circle (ENU) | ||
| pos = center + r * cos(turn_angle) * e1 + r * sin(turn_angle) * e2 | ||
|
|
||
| # z_kite (ENU): points down along the tether, from kite toward ground station | ||
| z_kite = -normalize(pos) | ||
|
|
||
| # Tangent vector = flight direction (ENU) | ||
| tangent = normalize(-sin(turn_angle) * e1 + cos(turn_angle) * e2) | ||
|
|
||
| # x_kite (ENU): leading edge direction, must be ⊥ z_kite | ||
| # Orthogonalize tangent against z_kite | ||
| x_kite = normalize(tangent - dot(tangent, z_kite) * z_kite) | ||
|
|
||
| # y_kite (ENU): to the right looking in flight direction | ||
| y_kite = cross(z_kite, x_kite) | ||
|
|
||
| # Compute rotation matrix and extract Euler angles (roll, pitch, yaw) w.r.t. NED | ||
| rotation = calc_orient_rot(x_kite, y_kite, z_kite) | ||
| q = QuatRotation(rotation) | ||
| roll, pitch, yaw = quat2euler(q) | ||
|
|
||
| return (roll, pitch, yaw) | ||
| end | ||
|
|
||
| """ | ||
| calc_kite_heading(turn_angle; x=100.0, z=0.0, r=20.0) | ||
|
|
||
| Calculate the heading of the kite in radian for a given turn angle in radian. | ||
| The heading is the direction the nose of the kite is pointing to, where 0 means | ||
| the kite is flying upwards (in the SE reference frame). | ||
|
|
||
| Uses `calc_orientation` to get the kite orientation and `calc_elevation_azimuth` to get | ||
| the elevation and azimuth, then calls `calc_heading` from KiteUtils.jl. | ||
|
|
||
| # Arguments | ||
| - `turn_angle`: the position of the kite on the circle in radian (0 to 2π). | ||
| - `x`: distance of the circle center from the ground station along the east axis [m]. Default: 100.0. | ||
| - `z`: height of the circle center above the ground [m]. Default: 0.0. | ||
| - `r`: radius of the circle [m]. Default: 20.0. | ||
|
|
||
| # Returns | ||
| The heading angle in radian. | ||
| """ | ||
| function calc_kite_heading(turn_angle; x=100.0, z=0.0, r=20.0) | ||
| orientation = collect(calc_orientation(turn_angle; x=x, z=z, r=r)) | ||
| el, az = calc_elevation_azimuth(turn_angle; x=x, z=z, r=r) | ||
| calc_heading(orientation, el, az; respos=false) | ||
| end | ||
|
|
||
| # # Test the function calc_elevation_azimuth | ||
| # println("turn_angle => (elevation, azimuth)") | ||
| # for turn_angle in 0:30:360 | ||
| # el, az = calc_elevation_azimuth(deg2rad(turn_angle)) | ||
| # println(" $(turn_angle)deg => elevation: $(round(rad2deg(el), digits=2))deg, azimuth: $(round(rad2deg(az), digits=2))deg") | ||
| # end | ||
|
|
||
| # # Test the function calc_orientation | ||
| # println("\nturn_angle => (roll, pitch, yaw)") | ||
| # for turn_angle in 0:30:360 | ||
| # roll, pitch, yaw = calc_orientation(deg2rad(turn_angle)) | ||
| # println(" $(turn_angle)deg => roll: $(round(rad2deg(roll), digits=2))deg, pitch: $(round(rad2deg(pitch), digits=2))deg, yaw: $(round(rad2deg(yaw), digits=2))deg") | ||
| # end | ||
|
|
||
| # # Test the function calc_kite_heading | ||
| # println("\nturn_angle => heading") | ||
| # for turn_angle in 0:30:360 | ||
| # heading = calc_kite_heading(deg2rad(turn_angle)) | ||
| # println(" $(turn_angle)deg => heading: $(round(rad2deg(heading), digits=2))deg") | ||
| # end | ||
|
|
||
| # Helper: kite position on circle | ||
| function calc_kite_pos(turn_angle; x=100.0, z=0.0, r=20.0) | ||
| center = [x, 0.0, z] | ||
| e1, e2 = calc_circle_basis(x, z) | ||
| return center + r * cos(turn_angle) * e1 + r * sin(turn_angle) * e2 | ||
| end | ||
|
|
||
| # Compute data for multiple θ values | ||
| const theta = [30, 45, 60, 75] | ||
| turn_angles = 0:1:360 | ||
| max_height = 50.0 # Desired max height at the top of the circle (turn_angle=0) | ||
|
|
||
| ys_all = Vector{Vector{Float64}}() | ||
| zs_all = Vector{Vector{Float64}}() | ||
| headings_all = Vector{Vector{Float64}}() | ||
| psi_dot_all = Vector{Vector{Float64}}() | ||
| y_labels = String[] | ||
| z_labels = String[] | ||
| h_labels = String[] | ||
| pd_labels = String[] | ||
|
|
||
| for θ in theta | ||
| local r | ||
| r = max_height * sin(deg2rad(θ)) # Set r such that the kite is at max_height at the top of the circle | ||
| x = r / tan(deg2rad(θ)) | ||
| push!(ys_all, [calc_kite_pos(deg2rad(ta); x=x, z=0.0, r=r)[2] for ta in turn_angles]) | ||
| push!(zs_all, [calc_kite_pos(deg2rad(ta); x=x, z=0.0, r=r)[3] for ta in turn_angles]) | ||
| push!(headings_all, [rad2deg(calc_kite_heading(deg2rad(ta); x=x, z=0.0, r=r)) for ta in turn_angles]) | ||
| push!(y_labels, "y (θ=$(θ)°)") | ||
| push!(z_labels, "z (θ=$(θ)°)") | ||
| push!(h_labels, "Ψ (θ=$(θ)°)") | ||
| # Compute Ψ̇ = dΨ/d(turn_angle) using finite differences | ||
| # First unwrap the heading to remove ±180° discontinuities | ||
| h = headings_all[end] | ||
| h_unwrap = copy(h) | ||
| for j in 2:length(h_unwrap) | ||
| while h_unwrap[j] - h_unwrap[j-1] > 180 | ||
| h_unwrap[j] -= 360 | ||
| end | ||
| while h_unwrap[j] - h_unwrap[j-1] < -180 | ||
| h_unwrap[j] += 360 | ||
| end | ||
| end | ||
| dt = 1.0 # turn_angle step in degrees | ||
| dh = similar(h) | ||
| for j in 2:length(h_unwrap)-1 | ||
| dh[j] = (h_unwrap[j+1] - h_unwrap[j-1]) / (2 * dt) | ||
| end | ||
| dh[1] = (h_unwrap[2] - h_unwrap[1]) / dt | ||
| dh[end] = (h_unwrap[end] - h_unwrap[end-1]) / dt | ||
| push!(psi_dot_all, dh) | ||
| push!(pd_labels, "\$\\dot{\\Psi}\$ (θ=$(θ)°)") | ||
| end | ||
|
|
||
| # Combined plot: "y and z" on top, "heading (Ψ)" in middle, "Ψ̇" on bottom | ||
| plt.figure("heading and position", figsize=(10, 11)) | ||
|
|
||
| plt.subplot(3, 1, 1) | ||
| colors = ["C0", "C1", "C2", "C3"] | ||
| for i in eachindex(theta) | ||
| y_lbl = i == 1 ? "y" : nothing | ||
| z_lbl = i == 1 ? "z" : nothing | ||
| plt.plot(collect(turn_angles), ys_all[i], "-", color=colors[i], label=y_lbl) | ||
| plt.plot(collect(turn_angles), zs_all[i], ":", color=colors[i], label=z_lbl) | ||
| end | ||
| plt.ylabel("y, z [m]") | ||
| yz_handles = [plt.matplotlib.lines.Line2D([0], [0], color="black", linestyle="-", label="y"), | ||
| plt.matplotlib.lines.Line2D([0], [0], color="black", linestyle=":", label="z")] | ||
| leg1 = plt.legend(handles=yz_handles, loc="lower left") | ||
| plt.gca().add_artist(leg1) | ||
| # Second legend for theta/color mapping | ||
| theta_handles = [plt.matplotlib.lines.Line2D([0], [0], color=colors[i], label="θ=$(theta[i])°") for i in eachindex(theta)] | ||
| plt.legend(handles=theta_handles, loc="lower right") | ||
| plt.grid(true) | ||
|
|
||
| plt.subplot(3, 1, 2) | ||
| for i in eachindex(theta) | ||
| plt.plot(collect(turn_angles), headings_all[i], label=h_labels[i]) | ||
| end | ||
| plt.ylabel("Ψ [°]") | ||
| plt.legend() | ||
| plt.grid(true) | ||
|
|
||
| plt.subplot(3, 1, 3) | ||
| for i in eachindex(theta) | ||
| plt.plot(collect(turn_angles), psi_dot_all[i], label=pd_labels[i]) | ||
| end | ||
| plt.xlabel("turn angle [°]") | ||
| plt.ylabel("\$\\dot{\\Psi}\$ [°/°]") | ||
| plt.legend() | ||
| plt.grid(true) | ||
|
|
||
| plt.tight_layout() | ||
| plt.show(block=false) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.