|
| 1 | +#! /usr/bin/python |
| 2 | + |
| 3 | +""" |
| 4 | +Example of using the Dead Reckoning module to extrapolate entity position. |
| 5 | +
|
| 6 | +This script simulates receiving an EntityStatePdu with velocity and acceleration, |
| 7 | +and then uses the DeadReckoning module to predict the entity's position |
| 8 | +over a period of time. |
| 9 | +""" |
| 10 | + |
| 11 | +import time |
| 12 | +import numpy as np |
| 13 | +from opendis.dis7 import EntityStatePdu, Vector3Float |
| 14 | +from opendis import DeadReckoning |
| 15 | + |
| 16 | +def run_demo(): |
| 17 | + # 1. Create a mock PDU (as if received from the network) |
| 18 | + pdu = EntityStatePdu() |
| 19 | + |
| 20 | + # Initial State (t=0) |
| 21 | + # Position: Origin (0, 0, 0) |
| 22 | + pdu.entityLocation.x = 0.0 |
| 23 | + pdu.entityLocation.y = 0.0 |
| 24 | + pdu.entityLocation.z = 0.0 |
| 25 | + |
| 26 | + # Velocity: 10 m/s in X direction |
| 27 | + pdu.entityLinearVelocity.x = 10.0 |
| 28 | + pdu.entityLinearVelocity.y = 0.0 |
| 29 | + pdu.entityLinearVelocity.z = 0.0 |
| 30 | + |
| 31 | + # Acceleration: 1 m/s^2 in X direction |
| 32 | + pdu.deadReckoningParameters.entityLinearAcceleration.x = 1.0 |
| 33 | + pdu.deadReckoningParameters.entityLinearAcceleration.y = 0.0 |
| 34 | + pdu.deadReckoningParameters.entityLinearAcceleration.z = 0.0 |
| 35 | + |
| 36 | + # Orientation: Facing East (0, 0, 0) |
| 37 | + pdu.entityOrientation.psi = 0.0 |
| 38 | + pdu.entityOrientation.theta = 0.0 |
| 39 | + pdu.entityOrientation.phi = 0.0 |
| 40 | + |
| 41 | + # Angular Velocity: None |
| 42 | + pdu.deadReckoningParameters.entityAngularVelocity.x = 0.0 |
| 43 | + pdu.deadReckoningParameters.entityAngularVelocity.y = 0.0 |
| 44 | + pdu.deadReckoningParameters.entityAngularVelocity.z = 0.0 |
| 45 | + |
| 46 | + # Extract numpy arrays for the DR function |
| 47 | + # Note: With the new API, we can pass lists or tuples directly if we wanted to, |
| 48 | + # but since we are extracting from PDU objects, we'll stick with what we have or use lists. |
| 49 | + # The API will convert them to numpy arrays internally. |
| 50 | + |
| 51 | + initial_pos = [pdu.entityLocation.x, pdu.entityLocation.y, pdu.entityLocation.z] |
| 52 | + velocity = [pdu.entityLinearVelocity.x, pdu.entityLinearVelocity.y, pdu.entityLinearVelocity.z] |
| 53 | + acceleration = [ |
| 54 | + pdu.deadReckoningParameters.entityLinearAcceleration.x, |
| 55 | + pdu.deadReckoningParameters.entityLinearAcceleration.y, |
| 56 | + pdu.deadReckoningParameters.entityLinearAcceleration.z |
| 57 | + ] |
| 58 | + orientation = [pdu.entityOrientation.psi, pdu.entityOrientation.theta, pdu.entityOrientation.phi] |
| 59 | + angular_velocity = [ |
| 60 | + pdu.deadReckoningParameters.entityAngularVelocity.x, |
| 61 | + pdu.deadReckoningParameters.entityAngularVelocity.y, |
| 62 | + pdu.deadReckoningParameters.entityAngularVelocity.z |
| 63 | + ] |
| 64 | + |
| 65 | + print("--- Dead Reckoning Demonstration ---") |
| 66 | + print(f"Initial Position: {initial_pos}") |
| 67 | + print(f"Velocity: {velocity} m/s") |
| 68 | + print(f"Acceleration: {acceleration} m/s^2") |
| 69 | + print("Simulating 5 seconds of movement using RVW (Rotation, Velocity, World) algorithm...\n") |
| 70 | + |
| 71 | + # 2. Simulate extrapolation loop |
| 72 | + start_time = time.time() |
| 73 | + simulation_duration = 5.0 # seconds |
| 74 | + |
| 75 | + # We will simulate 'frames' |
| 76 | + current_sim_time = 0.0 |
| 77 | + step = 0.5 # Update every 0.5 seconds |
| 78 | + |
| 79 | + while current_sim_time <= simulation_duration: |
| 80 | + # Calculate new position using RVW algorithm |
| 81 | + # Position = P0 + V0*t + 0.5*A0*t^2 |
| 82 | + new_pos, new_ori = DeadReckoning.drm_rvw( |
| 83 | + initial_pos, |
| 84 | + velocity, |
| 85 | + acceleration, |
| 86 | + current_sim_time, |
| 87 | + orientation, |
| 88 | + angular_velocity |
| 89 | + ) |
| 90 | + |
| 91 | + print(f"Time: {current_sim_time:.1f}s | Extrapolated Pos: {new_pos}") |
| 92 | + |
| 93 | + current_sim_time += step |
| 94 | + # time.sleep(0.1) # Uncomment to run in real-time |
| 95 | + |
| 96 | + print("\n--- End of Demonstration ---") |
| 97 | + print("Notice how the X position increases quadratically due to acceleration.") |
| 98 | + print("T=0: 0.0") |
| 99 | + print("T=1: 10*1 + 0.5*1*1^2 = 10.5") |
| 100 | + print("T=2: 10*2 + 0.5*1*2^2 = 22.0") |
| 101 | + # 3. Demonstrate integration with RangeCoordinates (GPS) |
| 102 | + # The Dead Reckoning module returns ECEF coordinates (Earth-Centered, Earth-Fixed). |
| 103 | + # Usually, users want Latitude/Longitude/Altitude. |
| 104 | + from opendis.RangeCoordinates import GPS, deg2rad, rad2deg |
| 105 | + |
| 106 | + gps = GPS() |
| 107 | + |
| 108 | + print("\n--- Integration Verification ---") |
| 109 | + print("Converting extrapolated ECEF coordinates to Lat/Lon/Alt using opendis.RangeCoordinates...") |
| 110 | + |
| 111 | + # Let's take the final position from the simulation |
| 112 | + final_pos_ecef = (new_pos[0], new_pos[1], new_pos[2]) |
| 113 | + |
| 114 | + # Convert ECEF to LLA |
| 115 | + # Note: Our example started at 0,0,0 which is the center of the earth (invalid for LLA usually), |
| 116 | + # but let's assume the initial position was actually on the surface for this part of the demo |
| 117 | + # or just show the conversion call structure. |
| 118 | + |
| 119 | + # To make this realistic, let's restart with a real-world location. |
| 120 | + # Monterey, CA: 36.6 N, 121.9 W |
| 121 | + print("\nRestarting simulation at Monterey, CA...") |
| 122 | + start_lat = 36.6 |
| 123 | + start_lon = -121.9 |
| 124 | + start_alt = 100.0 # meters |
| 125 | + |
| 126 | + start_ecef = gps.lla2ecef((start_lat, start_lon, start_alt)) |
| 127 | + print(f"Start LLA: ({start_lat}, {start_lon}, {start_alt})") |
| 128 | + print(f"Start ECEF: {start_ecef}") |
| 129 | + |
| 130 | + # Move East (Velocity in Y in ECEF is roughly East at this location? No, it's complex in ECEF.) |
| 131 | + # For simplicity, let's just add the displacement we calculated earlier to this valid ECEF point. |
| 132 | + # This isn't physically perfect (earth is curved), but demonstrates the API integration. |
| 133 | + |
| 134 | + displacement = new_pos # The 62.5m displacement from the previous run |
| 135 | + final_ecef_real = ( |
| 136 | + start_ecef[0] + displacement[0], |
| 137 | + start_ecef[1] + displacement[1], |
| 138 | + start_ecef[2] + displacement[2] |
| 139 | + ) |
| 140 | + |
| 141 | + final_lla = gps.ecef2lla(final_ecef_real) |
| 142 | + print(f"Final ECEF: {final_ecef_real}") |
| 143 | + print(f"Final LLA: ({final_lla[0]:.6f}, {final_lla[1]:.6f}, {final_lla[2]:.2f})") |
| 144 | + |
| 145 | + print("\nIntegration Successful: Dead Reckoning output was successfully fed into RangeCoordinates.") |
| 146 | + |
| 147 | +if __name__ == "__main__": |
| 148 | + run_demo() |
0 commit comments