Engee documentation
Notebook

Modelling drone flight over a 3D landscape

The presented code in Julia language implements the simulation of an unmanned aerial vehicle (UAV) flight over a generated 3D landscape. The programme includes several key steps.

  1. Landscape generation using trigonometric functions and random noise to create realistic terrain.
  2. Modelling the drone's motion, taking into account height, speed and obstacle detection constraints.
  3. Trajectory visualisation in 2D and 3D, as well as flight animation taking into account detected obstacles.

The code demonstrates the application of numerical methods, linear algebra and interactive graphics in Julia, making it useful for autonomous navigation, robotics and terrain analysis tasks.

Supporting libraries

Let's add auxiliary objects and libraries.

LinearAlgebra connects the built-in library for linear algebraic operations (matrices, vectors, expansions, norms, etc.).

plotlyjs() activates the PlotlyJS backend for interactive visualisation.

In [ ]:
using LinearAlgebra
plotlyjs()
Out[0]:
Plots.PlotlyJSBackend()

Generating a three-dimensional landscape

Function generate_landscape generates a 3D landscape of specified dimensions (width_m × length_m) and visualises it as an interactive surface.

  1. The function creates a coordinate grid: x and y - linear ranges from 0 to width_m and length_m with a step of 1 metre.
  2. It then fills in the elevations (z) - uses a combination of sine and cosine to create a hilly surface.
  3. And also adds random noise (rand()) for realism.
  4. In addition, the function defines random start and finish points for the drone. (green - start, red - finish).

Key Parameters

Parameter Type/Value Description
width_m Int Landscape width (metres).
length_m Int Landscape length (metres)
c=:terrain Symbol Colour map for terrain.
start/finish Vector{Float64} Coordinates [x, y, z].
In [ ]:
function generate_landscape(width_m, length_m)
    x = 0:1:width_m
    y = 0:1:length_m

    z = [10(sin(xi / 15) + cos(yi / 15)) + 5rand() + 10 for xi in x, yi in y]
    z = reverse(z, dims=(1, 2))

    random_yi_start = rand(1:length(y))
    random_yi_finish = rand(1:length(y))
    xi_start = 10
    xi_finish = length(x)-10
    start = [x[xi_start], y[random_yi_start], z[xi_start, random_yi_start]]
    finish = [x[xi_finish], y[random_yi_finish], z[xi_finish, random_yi_finish]]

    plt = surface(x, y, z', c=:terrain, xlabel="X (м)", ylabel="Y (м)", zlabel="Z (м)", title="3D ландшафт", legend=false)
    scatter!(plt, [start[1]], [start[2]], [start[3]+3], marker=(:circle), color=:green)
    scatter!(plt, [finish[1]], [finish[2]], [finish[3]+3], marker=(:circle), color=:red)

    display(plt)
    return x, y, z, start, finish
end

landscape = generate_landscape(100, 100);

Drone behaviour simulation

Algorithm of drone behaviour in simulation

1. Basic logic of movement

The drone moves from start point (start) to finish point (finish) in a straight path, respecting the constraints:

  • speed: the drone moves a fixed distance (speed) per step.
  • altitude: maintains altitude no lower than min_height above terrain and no higher than max_height.
  • boundary correction: does not fly outside the terrain (clamp).

2. Obstacle Detection

The drone scans the space in a field of view (FOV) of 45° with a set distance (view_distance):

  • checks points in the field of view (12 beams).
  • If the height of an obstacle (obstacle_height) threatens a collision (obstacle_height + min_height > текущая высота), adds it to the list visible_obstacles.

3. obstacle avoidance When obstacles are detected, the following options are available.

  • Option 1 (50% chance): shifts sideways (left/right at speed).
  • Option 2 (50% chance): goes above the maximum height of the obstacle + min_height + 1.0.

4. Landing

Upon reaching the finish line

  • gently descends in 10 steps to the elevation of the terrain at the finish point.

5. Visualisation

  • Graph 1: 2D trajectory (XY) with start (green) and finish (red) marks.
  • Graph 2: elevation change (Z) by steps.

Key parameters

Parameter Value Description
min_height -5 m Minimum height above the terrain
max_height 5 m Maximum flying height
speed 1 m/step Travelling speed
fov_angle 45° Viewing angle for obstacle detection
view_distance 10 m Scanning range

In [ ]:
function simulate_drone(landscape, min_height, max_height, speed; fov_angle=45.0, view_distance=10.0)
    x, y, z, start, finish = landscape
    drone_path = [copy(start)]
    drone_heights = [start[3]]
    obstacles = [[]]

    current_pos = copy(start)
    for _ in 1:1000
        if norm(current_pos[1:2] .- finish[1:2]) < speed
            break
        end
        direction = normalize(finish[1:2] .- current_pos[1:2])
        next_pos = current_pos[1:2] .+ direction * speed
        next_pos[1] = clamp(next_pos[1], x[1], x[end])
        next_pos[2] = clamp(next_pos[2], y[1], y[end])

        xi = argmin(abs.(x .- next_pos[1]))
        yi = argmin(abs.(y .- next_pos[2]))
        terrain_height = z[xi, yi]
        new_z = clamp(max(terrain_height + min_height, current_pos[3]), -Inf, max_height)

        visible_obstacles = Tuple{Float64, Float64, Float64}[]
        for angle in range(-fov_angle/2, fov_angle/2, length=12)
            rad = deg2rad(angle)
            dir = [direction[1]*cos(rad) - direction[2]*sin(rad),
                   direction[1]*sin(rad) + direction[2]*cos(rad)]
            check_pos = next_pos .+ dir * view_distance
            xi_check = argmin(abs.(x .- check_pos[1]))
            yi_check = argmin(abs.(y .- check_pos[2]))

            if 1  xi_check  size(z, 1) && 1  yi_check  size(z, 2)
                obstacle_height = z[xi_check, yi_check]
                if obstacle_height + min_height > new_z
                    push!(visible_obstacles, (x[xi_check], y[yi_check], obstacle_height))
                end
            end
        end

        if !isempty(visible_obstacles)
            max_obstacle = maximum(obs[3] for obs in visible_obstacles)
            if max_obstacle + min_height > new_z
                if rand() < 0.5
                    next_pos[1] = clamp(next_pos[1] + rand([-1.0, 1.0]) * speed, x[1], x[end])
                else
                    new_z = max_obstacle + min_height + 1.0
                end
            end
        end

        current_pos = [next_pos[1], next_pos[2], new_z]
        push!(drone_path, copy(current_pos))
        push!(drone_heights, new_z)
        push!(obstacles, visible_obstacles)
    end

    xi_end = argmin(abs.(x .- finish[1]))
    yi_end = argmin(abs.(y .- finish[2]))
    final_ground = z[xi_end, yi_end]

    for _ in 1:10
        new_z = drone_path[end][3] - (drone_path[end][3] - final_ground) / 10
        push!(drone_path, [finish[1], finish[2], new_z])
        push!(drone_heights, new_z)
        push!(obstacles, [])
    end

    println("Время полёта дрона: $(length(drone_path)) шагов")
    return drone_path, drone_heights, obstacles, start, finish
end

speed = 1
drone_path, drone_heights, obstacles, start, finish = simulate_drone(landscape, -5, 5, speed)

path_x = [p[1] for p in drone_path]
path_y = [p[2] for p in drone_path]
path_z = [p[3] for p in drone_path]
t = 1:length(drone_path)

plt1 = plot(path_x, path_y, lw=2, c=:blue, xlabel="X (м)", ylabel="Y (м)", title="Путь дрона", legend=false)
scatter!(plt1, [path_x[1]], [path_y[1]], marker=:circle, markersize=8, color=:green)
scatter!(plt1, [path_x[end]], [path_y[end]], marker=:circle, markersize=8, color=:red)

plt2 = plot(t, path_z, lw=2, c=:purple, xlabel="Шаги", ylabel="Высота (м)", title="Высоты дрона", legend=false)
scatter!(plt2, [t[1]], [path_z[1]], marker=:circle, markersize=8, color=:green)
scatter!(plt2, [t[end]], [path_z[end]], marker=:circle, markersize=8, color=:red)

plot(plt1, plt2, layout=(1, 2), size=(1000, 400))
Время полёта дрона: 109 шагов
Out[0]:

3. Drone flight visualisation

Drone flight visualisation algorithm

1. Graphics customisation

  • Backend: uses gr() for high performance rendering.
  • Frame Rate: defaults to fps=10 (adjustable by parameter).

2. Data Preparation

  • Landscape: split into coordinates x, y and elevations z.
  • Optimisation: dictionaries xidx, yidx are created for quick search of drone coordinate indices in the landscape grid.

3.Basic Graphics

  • 3D surface (base_surface):
    • colour scheme :terrain.
    • Start (green) and finish (red) marks.
  • 2D map (base_2d):
    • terrain contour plot (contour!) with transparency alpha=0.3.
    • start and finish points.

4. Flight animation

For each frame (i-th step of the path):

  1. 3D visualisation:
    • drone's current position (red dot),
    • dynamic boundaries (xlims!, ylims!) to focus on the area around the drone,
    • header with height above the terrain and an arrow (↑/↓) indicating altitude gain/decrease.
  2. 2D visualisation:
    • gradual display of the distance travelled (blue line),
    • current position (red dot).
  3. Obstacles:
    • marked with yellow crosses (:xcross) in 3D and 2D if detected in the current step.

5. animation assembly

  • frames are generated at intervals of 1/speed (but not less than 1 step),
  • the resulting animation is saved as GIF with specified fps.

Key parameters

Parameter Value/Type Description
fps 10 (default) Animation frame rate (frames/sec)
speed 1 (m/step) Drone speed, affects the smoothness of the animation
layout @layout([a{0.7w} b]) 3D to 2D graphics size ratio (70% / 30%)
In [ ]:
gr()

function visualize_flight(landscape, drone_path, drone_heights, obstacles, speed, start, finish; fps=10)
    x, y, z = landscape

    xidx = Dict(px => argmin(abs.(x .- px)) for px in unique(p[1] for p in drone_path))
    yidx = Dict(py => argmin(abs.(y .- py)) for py in unique(p[2] for p in drone_path))

    base_surface = surface(x, y, z', c=:terrain, legend=false, aspect_ratio=:auto, axis=false)
    base_2d = plot(aspect_ratio=1, legend=false, axis=false)
    contour!(base_2d, x, y, z', c=:terrain, alpha=0.3)
    scatter!(base_surface, [start[1]], [start[2]], [start[3]], marker=:circle, markercolor=:green, label=false)
    scatter!(base_surface, [finish[1]], [finish[2]], [z[end, end]], marker=:circle, markercolor=:red, label=false)
    scatter!(base_2d, [start[1]], [start[2]], marker=:circle, markercolor=:green)
    scatter!(base_2d, [finish[1]], [finish[2]], marker=:circle, markercolor=:red)

    anim = @animate for i in 1:length(drone_path)
        plt1 = deepcopy(base_surface)
        plt2 = deepcopy(base_2d)

        px, py, pz = drone_path[i]
        scatter!(plt1, [px], [py], [pz], markersize=5, markercolor=:red, marker=:circle)
        xlims!(plt1, px - 10, px + 10)
        ylims!(plt1, py - 10, py + 10)

        xi, yi = xidx[px], yidx[py]
        height_above = pz - z[xi, yi]
        arrow = i == 1 ? "↑" : (height_above > (drone_path[i - 1][3] - z[xidx[drone_path[i - 1][1]], yidx[drone_path[i - 1][2]]]) ? "↑" : "↓")
        title!(plt1, "Высота дрона: $(floor(height_above)) м $arrow")

        if i > 1
            path = drone_path[1:i]
            plot!(plt2, [p[1] for p in path], [p[2] for p in path], linewidth=2, linecolor=:blue)
        end

        scatter!(plt2, [px], [py], markersize=5, markercolor=:red)

        if !isempty(obstacles[i])
            obs_x = [o[1] for o in obstacles[i]]
            obs_y = [o[2] for o in obstacles[i]]
            obs_z = [o[3] for o in obstacles[i]]
            scatter!(plt1, obs_x, obs_y, obs_z, markersize=3, markercolor=:yellow, marker=:xcross)
            scatter!(plt2, obs_x, obs_y, markersize=3, markercolor=:yellow, marker=:xcross)
        end

        plot(plt1, plt2, layout = @layout([a{0.7w} b]), size=(900, 300))
    end every max(1, round(Int, 1/speed))

    gif(anim, fps=fps)
end

visualize_flight(landscape, drone_path, drone_heights, obstacles, speed, start, finish; fps=5)
[ Info: Saved animation to /user/start/examples/codegen/tmp.gif
Out[0]:
No description has been provided for this image

Conclusion

This code is a complete solution for simulating drone flight over a random landscape. Let's list its main features.

  1. Flexible parameters: you can adjust the speed, flight altitude, viewing angle and obstacle detection range.
  2. Realistic visualisation: 3D graphics and animation are used to visualise the trajectory.
  3. Obstacle avoidance algorithm: the drone dynamically adjusts its route to avoid collisions.

This code can serve as a basis for more complex simulations like autonomous flights in urban environments or terrain mapping. Further development could include integrating machine learning to optimise routes or processing data from real sensors.