代码拉取完成,页面将自动刷新
#!/usr/bin/env python
'''
useful extra functions for use by mavlink clients
Copyright Andrew Tridgell 2011
Released under GNU GPL version 3 or later
'''
from __future__ import print_function
from __future__ import absolute_import
from builtins import object
from builtins import sum as builtin_sum
from math import *
try:
# in case numpy isn't installed
from .quaternion import Quaternion
except:
pass
try:
# rotmat doesn't work on Python3.2 yet
from .rotmat import Vector3, Matrix3
except Exception:
pass
def kmh(mps):
'''convert m/s to Km/h'''
return mps*3.6
def altitude(SCALED_PRESSURE, ground_pressure=None, ground_temp=None):
'''calculate barometric altitude'''
from . import mavutil
self = mavutil.mavfile_global
if ground_pressure is None:
if self.param('GND_ABS_PRESS', None) is None:
return 0
ground_pressure = self.param('GND_ABS_PRESS', 1)
if ground_temp is None:
ground_temp = self.param('GND_TEMP', 0)
scaling = ground_pressure / (SCALED_PRESSURE.press_abs*100.0)
temp = ground_temp + 273.15
return log(scaling) * temp * 29271.267 * 0.001
def altitude2(SCALED_PRESSURE, ground_pressure=None, ground_temp=None):
'''calculate barometric altitude'''
from . import mavutil
self = mavutil.mavfile_global
if ground_pressure is None:
if self.param('GND_ABS_PRESS', None) is None:
return 0
ground_pressure = self.param('GND_ABS_PRESS', 1)
if ground_temp is None:
ground_temp = self.param('GND_TEMP', 0)
scaling = SCALED_PRESSURE.press_abs*100.0 / ground_pressure
temp = ground_temp + 273.15
return 153.8462 * temp * (1.0 - exp(0.190259 * log(scaling)))
def mag_heading(RAW_IMU, ATTITUDE, declination=None, SENSOR_OFFSETS=None, ofs=None):
'''calculate heading from raw magnetometer'''
if declination is None:
from . import mavutil
declination = degrees(mavutil.mavfile_global.param('COMPASS_DEC', 0))
mag_x = RAW_IMU.xmag
mag_y = RAW_IMU.ymag
mag_z = RAW_IMU.zmag
if SENSOR_OFFSETS is not None and ofs is not None:
mag_x += ofs[0] - SENSOR_OFFSETS.mag_ofs_x
mag_y += ofs[1] - SENSOR_OFFSETS.mag_ofs_y
mag_z += ofs[2] - SENSOR_OFFSETS.mag_ofs_z
# go via a DCM matrix to match the APM calculation
dcm_matrix = rotation(ATTITUDE)
cos_pitch_sq = 1.0-(dcm_matrix.c.x*dcm_matrix.c.x)
headY = mag_y * dcm_matrix.c.z - mag_z * dcm_matrix.c.y
headX = mag_x * cos_pitch_sq - dcm_matrix.c.x * (mag_y * dcm_matrix.c.y + mag_z * dcm_matrix.c.z)
heading = degrees(atan2(-headY,headX)) + declination
if heading < 0:
heading += 360
return heading
def mag_field_df(MAG, ofs=None, diagonals=(1.0,1.0,1.0), offdiagonals=(0.0,0.0,0.0)):
'''calculate magnetic field strength from raw magnetometer for DF '''
mag = Vector3(MAG.MagX, MAG.MagY, MAG.MagZ)
if ofs is not None:
mag += Vector3(ofs[0],ofs[1],ofs[2]) - Vector3(MAG.OfsX, MAG.OfsY, MAG.OfsZ)
diagonals = Vector3(diagonals[0], diagonals[1], diagonals[2])
offdiagonals = Vector3(offdiagonals[0], offdiagonals[1], offdiagonals[2])
rot = Matrix3(Vector3(diagonals.x, offdiagonals.x, offdiagonals.y),
Vector3(offdiagonals.x, diagonals.y, offdiagonals.z),
Vector3(offdiagonals.y, offdiagonals.z, diagonals.z))
mag = rot * mag
return mag.length()
def mag_heading_df(MAG, ATT, declination=None, ofs=None, diagonals=(1.0,1.0,1.0), offdiagonals=(0.0,0.0,0.0)):
'''calculate heading from raw magnetometer'''
if declination is None:
from pymavlink import mavutil
declination = degrees(mavutil.mavfile_global.param('COMPASS_DEC', 0))
mag = Vector3(MAG.MagX,MAG.MagY,MAG.MagZ)
if ofs is not None:
mag += Vector3(ofs[0],ofs[1],ofs[2]) - Vector3(MAG.OfsX, MAG.OfsY, MAG.OfsZ)
diagonals = Vector3(diagonals[0], diagonals[1], diagonals[2])
offdiagonals = Vector3(offdiagonals[0], offdiagonals[1], offdiagonals[2])
rot = Matrix3(Vector3(diagonals.x, offdiagonals.x, offdiagonals.y),
Vector3(offdiagonals.x, diagonals.y, offdiagonals.z),
Vector3(offdiagonals.y, offdiagonals.z, diagonals.z))
mag = rot * mag
# go via a DCM matrix to match the APM calculation
dcm_matrix = rotation_df(ATT)
cos_pitch_sq = 1.0-(dcm_matrix.c.x*dcm_matrix.c.x)
headY = mag.y * dcm_matrix.c.z - mag.z * dcm_matrix.c.y
headX = mag.x * cos_pitch_sq - dcm_matrix.c.x * (mag.y * dcm_matrix.c.y + mag.z * dcm_matrix.c.z)
heading = degrees(atan2(-headY,headX)) + declination
if heading < 0:
heading += 360
return heading
def gps_time_to_epoch(week, msec):
'''convert GPS week and TOW to a time in seconds since 1970'''
epoch = 86400*(10*365 + int((1980-1969)/4) + 1 + 6 - 2)
return epoch + 86400*7*week + msec*0.001 - 18
def mag_heading_motors(RAW_IMU, ATTITUDE, declination, SENSOR_OFFSETS, ofs, SERVO_OUTPUT_RAW, motor_ofs):
'''calculate heading from raw magnetometer'''
ofs = get_motor_offsets(SERVO_OUTPUT_RAW, ofs, motor_ofs)
if declination is None:
from . import mavutil
declination = degrees(mavutil.mavfile_global.param('COMPASS_DEC', 0))
mag_x = RAW_IMU.xmag
mag_y = RAW_IMU.ymag
mag_z = RAW_IMU.zmag
if SENSOR_OFFSETS is not None and ofs is not None:
mag_x += ofs[0] - SENSOR_OFFSETS.mag_ofs_x
mag_y += ofs[1] - SENSOR_OFFSETS.mag_ofs_y
mag_z += ofs[2] - SENSOR_OFFSETS.mag_ofs_z
headX = mag_x*cos(ATTITUDE.pitch) + mag_y*sin(ATTITUDE.roll)*sin(ATTITUDE.pitch) + mag_z*cos(ATTITUDE.roll)*sin(ATTITUDE.pitch)
headY = mag_y*cos(ATTITUDE.roll) - mag_z*sin(ATTITUDE.roll)
heading = degrees(atan2(-headY,headX)) + declination
if heading < 0:
heading += 360
return heading
def mag_field(RAW_IMU, SENSOR_OFFSETS=None, ofs=None):
'''calculate magnetic field strength from raw magnetometer'''
mag_x = RAW_IMU.xmag
mag_y = RAW_IMU.ymag
mag_z = RAW_IMU.zmag
if SENSOR_OFFSETS is not None and ofs is not None:
mag_x += ofs[0] - SENSOR_OFFSETS.mag_ofs_x
mag_y += ofs[1] - SENSOR_OFFSETS.mag_ofs_y
mag_z += ofs[2] - SENSOR_OFFSETS.mag_ofs_z
return sqrt(mag_x**2 + mag_y**2 + mag_z**2)
def mag_field_df(MAG, ofs=None):
'''calculate magnetic field strength from raw magnetometer (dataflash version)'''
mag = Vector3(MAG.MagX, MAG.MagY, MAG.MagZ)
offsets = Vector3(MAG.OfsX, MAG.OfsY, MAG.OfsZ)
if ofs is not None:
mag = (mag - offsets) + Vector3(ofs[0], ofs[1], ofs[2])
return mag.length()
def get_motor_offsets(SERVO_OUTPUT_RAW, ofs, motor_ofs):
'''calculate magnetic field strength from raw magnetometer'''
from . import mavutil
self = mavutil.mavfile_global
m = SERVO_OUTPUT_RAW
motor_pwm = m.servo1_raw + m.servo2_raw + m.servo3_raw + m.servo4_raw
motor_pwm *= 0.25
rc3_min = self.param('RC3_MIN', 1100)
rc3_max = self.param('RC3_MAX', 1900)
motor = (motor_pwm - rc3_min) / (rc3_max - rc3_min)
if motor > 1.0:
motor = 1.0
if motor < 0.0:
motor = 0.0
motor_offsets0 = motor_ofs[0] * motor
motor_offsets1 = motor_ofs[1] * motor
motor_offsets2 = motor_ofs[2] * motor
ofs = (ofs[0] + motor_offsets0, ofs[1] + motor_offsets1, ofs[2] + motor_offsets2)
return ofs
def mag_field_motors(RAW_IMU, SENSOR_OFFSETS, ofs, SERVO_OUTPUT_RAW, motor_ofs):
'''calculate magnetic field strength from raw magnetometer'''
mag_x = RAW_IMU.xmag
mag_y = RAW_IMU.ymag
mag_z = RAW_IMU.zmag
ofs = get_motor_offsets(SERVO_OUTPUT_RAW, ofs, motor_ofs)
if SENSOR_OFFSETS is not None and ofs is not None:
mag_x += ofs[0] - SENSOR_OFFSETS.mag_ofs_x
mag_y += ofs[1] - SENSOR_OFFSETS.mag_ofs_y
mag_z += ofs[2] - SENSOR_OFFSETS.mag_ofs_z
return sqrt(mag_x**2 + mag_y**2 + mag_z**2)
def angle_diff(angle1, angle2):
'''show the difference between two angles in degrees'''
ret = angle1 - angle2
if ret > 180:
ret -= 360;
if ret < -180:
ret += 360
return ret
average_data = {}
def average(var, key, N):
'''average over N points'''
global average_data
if not key in average_data:
average_data[key] = [var]*N
return var
l = average_data[key]
l.pop(0)
l.append(var)
return builtin_sum(l) / N
derivative_data = {}
def second_derivative_5(var, key):
'''5 point 2nd derivative'''
global derivative_data
from . import mavutil
tnow = mavutil.mavfile_global.timestamp
if not key in derivative_data:
derivative_data[key] = (tnow, [var]*5)
return 0
(last_time, data) = derivative_data[key]
data.pop(0)
data.append(var)
derivative_data[key] = (tnow, data)
h = (tnow - last_time)
# N=5 2nd derivative from
# http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
ret = ((data[4] + data[0]) - 2*data[2]) / (4*h**2)
return ret
def second_derivative_9(var, key):
'''9 point 2nd derivative'''
global derivative_data
from . import mavutil
tnow = mavutil.mavfile_global.timestamp
if not key in derivative_data:
derivative_data[key] = (tnow, [var]*9)
return 0
(last_time, data) = derivative_data[key]
data.pop(0)
data.append(var)
derivative_data[key] = (tnow, data)
h = (tnow - last_time)
# N=5 2nd derivative from
# http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
f = data
ret = ((f[8] + f[0]) + 4*(f[7] + f[1]) + 4*(f[6]+f[2]) - 4*(f[5]+f[3]) - 10*f[4])/(64*h**2)
return ret
lowpass_data = {}
def lowpass(var, key, factor):
'''a simple lowpass filter'''
global lowpass_data
if not key in lowpass_data:
lowpass_data[key] = var
else:
lowpass_data[key] = factor*lowpass_data[key] + (1.0 - factor)*var
return lowpass_data[key]
def lpalpha(sample_rate_hz, cutoff_hz):
'''find alpha for low pass filter'''
rc = 1.0 / (2*pi*cutoff_hz)
dt = 1.0 / sample_rate_hz
return 1.0 - dt/(dt+rc)
lowpass_hz_data = {}
def lowpassHz(var, key, sample_rate_hz, cutoff_hz):
'''a simple lowpass filter with specified frequency'''
global lowpass_hz_data
alpha = lpalpha(sample_rate_hz, cutoff_hz)
if not key in lowpass_hz_data:
lowpass_hz_data[key] = var
else:
lowpass_hz_data[key] = alpha*lowpass_hz_data[key] + (1.0-alpha)*var
return lowpass_hz_data[key]
last_diff = {}
def diff(var, key):
'''calculate differences between values'''
global last_diff
ret = 0
if not key in last_diff:
last_diff[key] = var
return 0
ret = var - last_diff[key]
last_diff[key] = var
return ret
last_delta = {}
def delta(var, key, tusec=None):
'''calculate slope'''
if isnan(var):
return None
global last_delta
if tusec is not None:
tnow = tusec * 1.0e-6
else:
from . import mavutil
tnow = mavutil.mavfile_global.timestamp
ret = 0
if key in last_delta:
(last_v, last_t, last_ret) = last_delta[key]
if last_t == tnow:
return last_ret
if tnow == last_t:
ret = 0
else:
ret = (var - last_v) / (tnow - last_t)
last_delta[key] = (var, tnow, ret)
return ret
last_sum = {}
def sum(var, key):
'''sum variable'''
global last_sum
ret = 0
if not key in last_sum:
last_sum[key] = 0
last_sum[key] += var
return last_sum[key]
last_integral = {}
def integral(var, key, timeus):
'''integrate variable'''
global last_integral
ret = 0
if not key in last_integral:
last_integral[key] = (0,timeus)
(lastsum,lastt) = last_integral[key]
dt = (timeus - lastt) * 1.0e-6
dv = var * dt
newv = lastsum + dv
last_integral[key] = (newv,timeus)
return newv
def delta_angle(var, key, tusec=None):
'''calculate slope of an angle'''
global last_delta
if tusec is not None:
tnow = tusec * 1.0e-6
else:
from . import mavutil
tnow = mavutil.mavfile_global.timestamp
dv = 0
ret = 0
if key in last_delta:
(last_v, last_t, last_ret) = last_delta[key]
if last_t == tnow:
return last_ret
if tnow == last_t:
ret = 0
else:
dv = var - last_v
if dv > 180:
dv -= 360
if dv < -180:
dv += 360
ret = dv / (tnow - last_t)
last_delta[key] = (var, tnow, ret)
return ret
def roll_estimate(RAW_IMU,GPS_RAW_INT=None,ATTITUDE=None,SENSOR_OFFSETS=None, ofs=None, mul=None,smooth=0.7):
'''estimate roll from accelerometer'''
rx = RAW_IMU.xacc * 9.81 / 1000.0
ry = RAW_IMU.yacc * 9.81 / 1000.0
rz = RAW_IMU.zacc * 9.81 / 1000.0
if ATTITUDE is not None and GPS_RAW_INT is not None:
ry -= ATTITUDE.yawspeed * GPS_RAW_INT.vel*0.01
rz += ATTITUDE.pitchspeed * GPS_RAW_INT.vel*0.01
if SENSOR_OFFSETS is not None and ofs is not None:
rx += SENSOR_OFFSETS.accel_cal_x
ry += SENSOR_OFFSETS.accel_cal_y
rz += SENSOR_OFFSETS.accel_cal_z
rx -= ofs[0]
ry -= ofs[1]
rz -= ofs[2]
if mul is not None:
rx *= mul[0]
ry *= mul[1]
rz *= mul[2]
return lowpass(degrees(-asin(ry/sqrt(rx**2+ry**2+rz**2))),'_roll',smooth)
def pitch_estimate(RAW_IMU, GPS_RAW_INT=None,ATTITUDE=None, SENSOR_OFFSETS=None, ofs=None, mul=None, smooth=0.7):
'''estimate pitch from accelerometer'''
rx = RAW_IMU.xacc * 9.81 / 1000.0
ry = RAW_IMU.yacc * 9.81 / 1000.0
rz = RAW_IMU.zacc * 9.81 / 1000.0
if ATTITUDE is not None and GPS_RAW_INT is not None:
ry -= ATTITUDE.yawspeed * GPS_RAW_INT.vel*0.01
rz += ATTITUDE.pitchspeed * GPS_RAW_INT.vel*0.01
if SENSOR_OFFSETS is not None and ofs is not None:
rx += SENSOR_OFFSETS.accel_cal_x
ry += SENSOR_OFFSETS.accel_cal_y
rz += SENSOR_OFFSETS.accel_cal_z
rx -= ofs[0]
ry -= ofs[1]
rz -= ofs[2]
if mul is not None:
rx *= mul[0]
ry *= mul[1]
rz *= mul[2]
return lowpass(degrees(asin(rx/sqrt(rx**2+ry**2+rz**2))),'_pitch',smooth)
def rotation(ATTITUDE):
'''return the current DCM rotation matrix'''
r = Matrix3()
if hasattr(ATTITUDE, 'roll'):
r.from_euler(ATTITUDE.roll, ATTITUDE.pitch, ATTITUDE.yaw)
else:
r.from_euler(radians(ATTITUDE.Roll), radians(ATTITUDE.Pitch), radians(ATTITUDE.Yaw))
return r
def mag_rotation(RAW_IMU, inclination, declination):
'''return an attitude rotation matrix that is consistent with the current mag
vector'''
m_body = Vector3(RAW_IMU.xmag, RAW_IMU.ymag, RAW_IMU.zmag)
m_earth = Vector3(m_body.length(), 0, 0)
r = Matrix3()
r.from_euler(0, -radians(inclination), radians(declination))
m_earth = r * m_earth
r.from_two_vectors(m_earth, m_body)
return r
def mag_pitch(RAW_IMU, inclination, declination):
'''estimate pithc from mag'''
m = mag_rotation(RAW_IMU, inclination, declination)
(r, p, y) = m.to_euler()
return degrees(p)
def mag_roll(RAW_IMU, inclination, declination):
'''estimate roll from mag'''
m = mag_rotation(RAW_IMU, inclination, declination)
(r, p, y) = m.to_euler()
return degrees(r)
def gravity(RAW_IMU, SENSOR_OFFSETS=None, ofs=None, mul=None, smooth=0.7):
'''estimate pitch from accelerometer'''
if hasattr(RAW_IMU, 'xacc'):
rx = RAW_IMU.xacc * 9.81 / 1000.0
ry = RAW_IMU.yacc * 9.81 / 1000.0
rz = RAW_IMU.zacc * 9.81 / 1000.0
else:
rx = RAW_IMU.AccX
ry = RAW_IMU.AccY
rz = RAW_IMU.AccZ
if SENSOR_OFFSETS is not None and ofs is not None:
rx += SENSOR_OFFSETS.accel_cal_x
ry += SENSOR_OFFSETS.accel_cal_y
rz += SENSOR_OFFSETS.accel_cal_z
rx -= ofs[0]
ry -= ofs[1]
rz -= ofs[2]
if mul is not None:
rx *= mul[0]
ry *= mul[1]
rz *= mul[2]
return sqrt(rx**2+ry**2+rz**2)
def pitch_sim(SIMSTATE, GPS_RAW):
'''estimate pitch from SIMSTATE accels'''
xacc = SIMSTATE.xacc - lowpass(delta(GPS_RAW.v,"v")*6.6, "v", 0.9)
zacc = SIMSTATE.zacc
zacc += SIMSTATE.ygyro * GPS_RAW.v;
if xacc/zacc >= 1:
return 0
if xacc/zacc <= -1:
return -0
return degrees(-asin(xacc/zacc))
ORGN = None
def get_origin():
global ORGN
if ORGN is not None:
return ORGN
from . import mavutil
self = mavutil.mavfile_global
ret = self.messages.get('ORGN', None)
if ret is None:
ret = self.messages.get('GPS', None)
if ret.Status < 3:
return None
ORGN = ret
return ret
# graph distance_two(GPS,XKF1[0])
def get_lat_lon_alt(MSG):
'''gets lat and lon in radians and alt in meters from a position msg'''
if hasattr(MSG, 'Lat') and hasattr(MSG, 'Lng'):
lat = radians(MSG.Lat)
lon = radians(MSG.Lng)
alt = MSG.Alt
elif hasattr(MSG, 'Lat') and hasattr(MSG, 'Lon'):
lat = radians(MSG.Lat)
lon = radians(MSG.Lon)
alt = MSG.Alt
elif hasattr(MSG, 'cog'):
lat = radians(MSG.lat)*1.0e-7
lon = radians(MSG.lon)*1.0e-7
alt = MSG.alt*0.001
elif hasattr(MSG,'lat') and hasattr(MSG,'lon'):
lat = radians(MSG.lat)
lon = radians(MSG.lon)
alt = MSG.alt*0.001
elif hasattr(MSG,'lat') and hasattr(MSG,'lng'):
lat = radians(MSG.lat)
lon = radians(MSG.lng)
alt = MSG.alt*0.001
elif hasattr(MSG, 'PN'):
# origin relative position from EKF
(lat,lon,alt) = ekf1_pos(MSG)
lat = radians(lat)
lon = radians(lon)
else:
return None
return (lat, lon, alt)
def _distance_two(MSG1, MSG2, horizontal=True):
'''
Return the distance between two points in metres
Calculated as the great-circle distance using 'haversine’ formula
(Ref: http://www.movable-type.co.uk/scripts/latlong.html)
Uses the globally-average earth radius value of 6371km
'''
# get_lat_lon_alt returns radians - so no need to convert here
(lat1, lon1, alt1) = get_lat_lon_alt(MSG1)
(lat2, lon2, alt2) = get_lat_lon_alt(MSG2)
dLat = lat2 - lat1
dLon = lon2 - lon1
a = sin(0.5*dLat)**2 + sin(0.5*dLon)**2 * cos(lat1) * cos(lat2)
c = 2.0 * atan2(sqrt(a), sqrt(1.0-a))
ground_dist = 6371 * 1000 * c
if horizontal:
return ground_dist
return sqrt(ground_dist**2 + (alt2-alt1)**2)
def distance_two(MSG1, MSG2, horizontal=True):
'''distance between two points'''
try:
return _distance_two(MSG1, MSG2)
except Exception as ex:
print(ex)
return None
first_fix = None
def distance_home(GPS_RAW):
'''distance from first fix point'''
global first_fix
if (hasattr(GPS_RAW, 'fix_type') and GPS_RAW.fix_type < 2) or \
(hasattr(GPS_RAW, 'Status') and GPS_RAW.Status < 2):
return 0
if first_fix is None:
first_fix = GPS_RAW
return 0
return distance_two(GPS_RAW, first_fix)
def sawtooth(ATTITUDE, amplitude=2.0, period=5.0):
'''sawtooth pattern based on uptime'''
mins = (ATTITUDE.usec * 1.0e-6)/60
p = fmod(mins, period*2)
if p < period:
return amplitude * (p/period)
return amplitude * (period - (p-period))/period
def rate_of_turn(speed, bank):
'''return expected rate of turn in degrees/s for given speed in m/s and
bank angle in degrees'''
if abs(speed) < 2 or abs(bank) > 80:
return 0
ret = degrees(9.81*tan(radians(bank))/speed)
return ret
def wingloading(bank):
'''return expected wing loading factor for a bank angle in radians'''
return 1.0/cos(bank)
def airspeed(VFR_HUD, ratio=None, used_ratio=None, offset=None):
'''recompute airspeed with a different ARSPD_RATIO'''
from . import mavutil
mav = mavutil.mavfile_global
if ratio is None:
ratio = 1.9936 # APM default
if used_ratio is None:
if 'ARSPD_RATIO' in mav.params:
used_ratio = mav.params['ARSPD_RATIO']
else:
print("no ARSPD_RATIO in mav.params")
used_ratio = ratio
if hasattr(VFR_HUD,'airspeed'):
airspeed = VFR_HUD.airspeed
else:
airspeed = VFR_HUD.Airspeed
airspeed_pressure = (airspeed**2) / used_ratio
if offset is not None:
airspeed_pressure += offset
if airspeed_pressure < 0:
airspeed_pressure = 0
airspeed = sqrt(airspeed_pressure * ratio)
return airspeed
def EAS2TAS(ARSP,GPS,BARO,ground_temp=25):
'''EAS2TAS from ARSP.Temp'''
tempK = ground_temp + 273.15 - 0.0065 * GPS.Alt
return sqrt(1.225 / (BARO.Press / (287.26 * tempK)))
SSL_AIR_DENSITY = 1.225
C_TO_KELVIN = 273.15
ISA_LAPSE_RATE = 0.0065
ISA_GAS_CONSTANT = 287.26
SSL_AIR_TEMPERATURE = 288.15
SSL_AIR_PRESSURE = 101325.01576
def SimpleAtmosphere(alt_km):
REARTH = 6369.0
GMR = 34.163195
# geometric to geopotential altitude
h = alt_km*REARTH/(alt_km+REARTH)
if (h < 11.0):
# Troposphere
theta = (SSL_AIR_TEMPERATURE - 6.5 * h) / SSL_AIR_TEMPERATURE
delta = pow(theta, GMR / 6.5)
else:
# Stratosphere
theta = 216.65 / SSL_AIR_TEMPERATURE
delta = 0.2233611 * exp(-GMR * (h - 11.0) / 216.65)
sigma = delta/theta
return (sigma, delta, theta)
def eas2tas(alt_m, groundtemp=25.0):
'''eas2tas from altitude in meters AMSL'''
(sigma, delta, theta) = SimpleAtmosphere(alt_m*0.001)
pressure = SSL_AIR_PRESSURE * delta
tempK = groundtemp + C_TO_KELVIN - ISA_LAPSE_RATE * alt_m
eas2tas_squared = SSL_AIR_DENSITY / (pressure / (ISA_GAS_CONSTANT * tempK))
return sqrt(eas2tas_squared)
def airspeed_tas(VFR_HUD,GLOBAL_POSITION_INT):
'''airspeed as true airspeed from VFR_HUD and GLOBAL_POSITION_INT'''
return eas2tas(GLOBAL_POSITION_INT.alt*0.001) * VFR_HUD.airspeed
def airspeed_ratio(VFR_HUD):
'''recompute airspeed with a different ARSPD_RATIO'''
from . import mavutil
mav = mavutil.mavfile_global
airspeed_pressure = (VFR_HUD.airspeed**2) / ratio
airspeed = sqrt(airspeed_pressure * ratio)
return airspeed
def airspeed_voltage(VFR_HUD, ratio=None):
'''back-calculate the voltage the airspeed sensor must have seen'''
from . import mavutil
mav = mavutil.mavfile_global
if ratio is None:
ratio = 1.9936 # APM default
if 'ARSPD_RATIO' in mav.params:
used_ratio = mav.params['ARSPD_RATIO']
else:
used_ratio = ratio
if 'ARSPD_OFFSET' in mav.params:
offset = mav.params['ARSPD_OFFSET']
else:
return -1
airspeed_pressure = (pow(VFR_HUD.airspeed,2)) / used_ratio
raw = airspeed_pressure + offset
SCALING_OLD_CALIBRATION = 204.8
voltage = 5.0 * raw / 4096
return voltage
def earth_rates(ATTITUDE):
'''return angular velocities in earth frame'''
from math import sin, cos, tan, fabs
p = ATTITUDE.rollspeed
q = ATTITUDE.pitchspeed
r = ATTITUDE.yawspeed
phi = ATTITUDE.roll
theta = ATTITUDE.pitch
psi = ATTITUDE.yaw
phiDot = p + tan(theta)*(q*sin(phi) + r*cos(phi))
thetaDot = q*cos(phi) - r*sin(phi)
if fabs(cos(theta)) < 1.0e-20:
theta += 1.0e-10
psiDot = (q*sin(phi) + r*cos(phi))/cos(theta)
return (phiDot, thetaDot, psiDot)
def roll_rate(ATTITUDE):
'''return roll rate in earth frame'''
(phiDot, thetaDot, psiDot) = earth_rates(ATTITUDE)
return phiDot
def pitch_rate(ATTITUDE):
'''return pitch rate in earth frame'''
(phiDot, thetaDot, psiDot) = earth_rates(ATTITUDE)
return thetaDot
def yaw_rate(ATTITUDE):
'''return yaw rate in earth frame'''
(phiDot, thetaDot, psiDot) = earth_rates(ATTITUDE)
return psiDot
def gps_velocity(GLOBAL_POSITION_INT):
'''return GPS velocity vector'''
return Vector3(GLOBAL_POSITION_INT.vx, GLOBAL_POSITION_INT.vy, GLOBAL_POSITION_INT.vz) * 0.01
def gps_velocity_old(GPS_RAW_INT):
'''return GPS velocity vector'''
return Vector3(GPS_RAW_INT.vel*0.01*cos(radians(GPS_RAW_INT.cog*0.01)),
GPS_RAW_INT.vel*0.01*sin(radians(GPS_RAW_INT.cog*0.01)), 0)
def gps_velocity_body(GPS_RAW_INT, ATTITUDE):
'''return GPS velocity vector in body frame'''
r = rotation(ATTITUDE)
return r.transposed() * Vector3(GPS_RAW_INT.vel*0.01*cos(radians(GPS_RAW_INT.cog*0.01)),
GPS_RAW_INT.vel*0.01*sin(radians(GPS_RAW_INT.cog*0.01)),
-tan(ATTITUDE.pitch)*GPS_RAW_INT.vel*0.01)
def earth_accel(IMU,ATT):
'''return earth frame acceleration vector'''
r = rotation(ATT)
if hasattr(IMU, 'xacc'):
accel = Vector3(IMU.xacc, IMU.yacc, IMU.zacc) * 9.81 * 0.001
else:
accel = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
return r * accel
def earth_gyro(IMU,ATT):
'''return earth frame gyro vector'''
r = rotation(ATT)
if hasattr(IMU, 'xgyro'):
gyro = Vector3(IMU.xgyro, IMU.ygyro, IMU.zgyro) * degrees(0.001)
else:
gyro = Vector3(IMU.GyrX, IMU.GyrY, IMU.GyrZ)
return r * gyro
def airspeed_energy_error(NAV_CONTROLLER_OUTPUT, VFR_HUD):
'''return airspeed energy error matching APM internals
This is positive when we are going too slow
'''
aspeed_cm = VFR_HUD.airspeed*100
target_airspeed = NAV_CONTROLLER_OUTPUT.aspd_error + aspeed_cm
airspeed_energy_error = ((target_airspeed*target_airspeed) - (aspeed_cm*aspeed_cm))*0.00005
return airspeed_energy_error
def energy_error(NAV_CONTROLLER_OUTPUT, VFR_HUD):
'''return energy error matching APM internals
This is positive when we are too low or going too slow
'''
aspeed_energy_error = airspeed_energy_error(NAV_CONTROLLER_OUTPUT, VFR_HUD)
alt_error = NAV_CONTROLLER_OUTPUT.alt_error*100
energy_error = aspeed_energy_error + alt_error*0.098
return energy_error
def rover_turn_circle(SERVO_OUTPUT_RAW):
'''return turning circle (diameter) in meters for steering_angle in degrees
'''
# this matches Toms slash
max_wheel_turn = 35
wheelbase = 0.335
wheeltrack = 0.296
steering_angle = max_wheel_turn * (SERVO_OUTPUT_RAW.servo1_raw - 1500) / 400.0
theta = radians(steering_angle)
return (wheeltrack/2) + (wheelbase/sin(theta))
def rover_yaw_rate(VFR_HUD, SERVO_OUTPUT_RAW):
'''return yaw rate in degrees/second given steering_angle and speed'''
max_wheel_turn=35
speed = VFR_HUD.groundspeed
# assume 1100 to 1900 PWM on steering
steering_angle = max_wheel_turn * (SERVO_OUTPUT_RAW.servo1_raw - 1500) / 400.0
if abs(steering_angle) < 1.0e-6 or abs(speed) < 1.0e-6:
return 0
d = rover_turn_circle(SERVO_OUTPUT_RAW)
c = pi * d
t = c / speed
rate = 360.0 / t
return rate
def rover_lat_accel(VFR_HUD, SERVO_OUTPUT_RAW):
'''return lateral acceleration in m/s/s'''
speed = VFR_HUD.groundspeed
yaw_rate = rover_yaw_rate(VFR_HUD, SERVO_OUTPUT_RAW)
accel = radians(yaw_rate) * speed
return accel
def demix1(servo1, servo2, gain=0.5):
'''de-mix a mixed servo output'''
s1 = servo1 - 1500
s2 = servo2 - 1500
out1 = (s1+s2)*gain
out2 = (s1-s2)*gain
return out1+1500
def demix2(servo1, servo2, gain=0.5):
'''de-mix a mixed servo output'''
s1 = servo1 - 1500
s2 = servo2 - 1500
out1 = (s1+s2)*gain
out2 = (s1-s2)*gain
return out2+1500
def mixer(servo1, servo2, mixtype=1, gain=0.5):
'''mix two servos'''
s1 = servo1 - 1500
s2 = servo2 - 1500
v1 = (s1-s2)*gain
v2 = (s1+s2)*gain
if mixtype == 2:
v2 = -v2
elif mixtype == 3:
v1 = -v1
elif mixtype == 4:
v1 = -v1
v2 = -v2
if v1 > 600:
v1 = 600
elif v1 < -600:
v1 = -600
if v2 > 600:
v2 = 600
elif v2 < -600:
v2 = -600
return (1500+v1,1500+v2)
def mix1(servo1, servo2, mixtype=1, gain=0.5):
'''de-mix a mixed servo output'''
(v1,v2) = mixer(servo1, servo2, mixtype=mixtype, gain=gain)
return v1
def mix2(servo1, servo2, mixtype=1, gain=0.5):
'''de-mix a mixed servo output'''
(v1,v2) = mixer(servo1, servo2, mixtype=mixtype, gain=gain)
return v2
def wrap_180(angle):
if angle > 180:
angle -= 360.0
if angle < -180:
angle += 360.0
return angle
def wrap_360(angle):
if angle > 360:
angle -= 360.0
if angle < 0:
angle += 360.0
return angle
class DCM_State(object):
'''DCM state object'''
def __init__(self, roll, pitch, yaw):
self.dcm = Matrix3()
self.dcm2 = Matrix3()
self.dcm.from_euler(radians(roll), radians(pitch), radians(yaw))
self.dcm2.from_euler(radians(roll), radians(pitch), radians(yaw))
self.mag = Vector3()
self.gyro = Vector3()
self.accel = Vector3()
self.gps = None
self.rate = 50.0
self.kp = 0.2
self.kp_yaw = 0.3
self.omega_P = Vector3()
self.omega_P_yaw = Vector3()
self.omega_I = Vector3() # (-0.00199045287445, -0.00653007719666, -0.00714212376624)
self.omega_I_sum = Vector3()
self.omega_I_sum_time = 0
self.omega = Vector3()
self.ra_sum = Vector3()
self.last_delta_angle = Vector3()
self.last_velocity = Vector3()
(self.roll, self.pitch, self.yaw) = self.dcm.to_euler()
(self.roll2, self.pitch2, self.yaw2) = self.dcm2.to_euler()
def update(self, gyro, accel, mag, GPS):
if self.gyro != gyro or self.accel != accel:
delta_angle = old_div((gyro+self.omega_I), self.rate)
self.dcm.rotate(delta_angle)
correction = self.last_delta_angle % delta_angle
#print (delta_angle - self.last_delta_angle) * 58.0
corrected_delta = delta_angle + 0.0833333 * correction
self.dcm2.rotate(corrected_delta)
self.last_delta_angle = delta_angle
self.dcm.normalize()
self.dcm2.normalize()
self.gyro = gyro
self.accel = accel
(self.roll, self.pitch, self.yaw) = self.dcm.to_euler()
(self.roll2, self.pitch2, self.yaw2) = self.dcm2.to_euler()
dcm_state = None
def DCM_update(IMU, ATT, MAG, GPS):
'''implement full DCM system'''
global dcm_state
if dcm_state is None:
dcm_state = DCM_State(ATT.Roll, ATT.Pitch, ATT.Yaw)
mag = Vector3(MAG.MagX, MAG.MagY, MAG.MagZ)
gyro = Vector3(IMU.GyrX, IMU.GyrY, IMU.GyrZ)
accel = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
accel2 = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
dcm_state.update(gyro, accel, mag, GPS)
return dcm_state
class PX4_State(object):
'''PX4 DCM state object'''
def __init__(self, roll, pitch, yaw, timestamp):
self.dcm = Matrix3()
self.dcm.from_euler(radians(roll), radians(pitch), radians(yaw))
self.gyro = Vector3()
self.accel = Vector3()
self.timestamp = timestamp
(self.roll, self.pitch, self.yaw) = self.dcm.to_euler()
def update(self, gyro, accel, timestamp):
if self.gyro != gyro or self.accel != accel:
delta_angle = gyro * (timestamp - self.timestamp)
self.timestamp = timestamp
self.dcm.rotate(delta_angle)
self.dcm.normalize()
self.gyro = gyro
self.accel = accel
(self.roll, self.pitch, self.yaw) = self.dcm.to_euler()
px4_state = None
def PX4_update(IMU, ATT):
'''implement full DCM using PX4 native SD log data'''
global px4_state
if px4_state is None:
px4_state = PX4_State(degrees(ATT.Roll), degrees(ATT.Pitch), degrees(ATT.Yaw), IMU._timestamp)
gyro = Vector3(IMU.GyroX, IMU.GyroY, IMU.GyroZ)
accel = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
px4_state.update(gyro, accel, IMU._timestamp)
return px4_state
_downsample_N = 0
def downsample(N):
'''conditional that is true on every Nth sample'''
global _downsample_N
_downsample_N = (_downsample_N + 1) % N
return _downsample_N == 0
def armed(HEARTBEAT):
'''return 1 if armed, 0 if not'''
from . import mavutil
if HEARTBEAT.type == mavutil.mavlink.MAV_TYPE_GCS:
self = mavutil.mavfile_global
if self.motors_armed():
return 1
return 0
if HEARTBEAT.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED:
return 1
return 0
def rotation_df(ATT):
'''return the current DCM rotation matrix'''
r = Matrix3()
r.from_euler(radians(ATT.Roll), radians(ATT.Pitch), radians(ATT.Yaw))
return r
def rotation2(AHRS2):
'''return the current DCM rotation matrix'''
r = Matrix3()
r.from_euler(AHRS2.roll, AHRS2.pitch, AHRS2.yaw)
return r
def earth_accel2(RAW_IMU,ATTITUDE):
'''return earth frame acceleration vector from AHRS2'''
r = rotation2(ATTITUDE)
accel = Vector3(RAW_IMU.xacc, RAW_IMU.yacc, RAW_IMU.zacc) * 9.81 * 0.001
return r * accel
def earth_accel_df(IMU,ATT):
'''return earth frame acceleration vector from df log'''
r = rotation_df(ATT)
accel = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
return r * accel
def earth_accel2_df(IMU,IMU2,ATT):
'''return earth frame acceleration vector from df log'''
r = rotation_df(ATT)
accel1 = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
accel2 = Vector3(IMU2.AccX, IMU2.AccY, IMU2.AccZ)
accel = 0.5 * (accel1 + accel2)
return r * accel
def gps_velocity_df(GPS):
'''return GPS velocity vector'''
vx = GPS.Spd * cos(radians(GPS.GCrs))
vy = GPS.Spd * sin(radians(GPS.GCrs))
return Vector3(vx, vy, GPS.VZ)
def distance_gps2(GPS, GPS2):
'''distance between two points'''
if GPS.TimeMS != GPS2.TimeMS:
# reject messages not time aligned
return None
return distance_two(GPS, GPS2)
# SI base unit for 1 Rgeo / equatorial radius
radius_of_earth = 6378100.0 # in meters
def wrap_valid_longitude(lon):
''' wrap a longitude value around to always have a value in the range
[-180, +180) i.e 0 => 0, 1 => 1, -1 => -1, 181 => -179, -181 => 179
'''
return (((lon + 180.0) % 360.0) - 180.0)
def gps_newpos(lat, lon, bearing, distance):
'''extrapolate latitude/longitude given a heading and distance
thanks to http://www.movable-type.co.uk/scripts/latlong.html
'''
import math
lat1 = math.radians(lat)
lon1 = math.radians(lon)
brng = math.radians(bearing)
dr = distance/radius_of_earth
lat2 = math.asin(math.sin(lat1)*math.cos(dr) +
math.cos(lat1)*math.sin(dr)*math.cos(brng))
lon2 = lon1 + math.atan2(math.sin(brng)*math.sin(dr)*math.cos(lat1),
math.cos(dr)-math.sin(lat1)*math.sin(lat2))
return (math.degrees(lat2), wrap_valid_longitude(math.degrees(lon2)))
def gps_offset(lat, lon, east, north):
'''return new lat/lon after moving east/north
by the given number of meters'''
import math
bearing = math.degrees(math.atan2(east, north))
distance = math.sqrt(east**2 + north**2)
return gps_newpos(lat, lon, bearing, distance)
ekf_origin = None
def ekf1_pos(EKF1):
'''calculate EKF position when EKF disabled'''
global ekf_origin
from . import mavutil
self = mavutil.mavfile_global
if ekf_origin is None:
if not 'ORGN' in self.messages:
return None
ekf_origin = self.messages['ORGN']
(ekf_origin.Lat, ekf_origin.Lng) = (ekf_origin.Lat, ekf_origin.Lng)
(lat,lon) = gps_offset(ekf_origin.Lat, ekf_origin.Lng, EKF1.PE, EKF1.PN)
alt = ekf_origin.Alt - EKF1.PD
return (lat, lon, alt)
def quat_to_euler(q):
'''
Get Euler angles from a quaternion
:param q: quaternion [w, x, y , z]
:returns: euler angles [roll, pitch, yaw]
'''
quat = Quaternion(q)
return quat.euler
def euler_to_quat(e):
'''
Get quaternion from euler angles
:param e: euler angles [roll, pitch, yaw]
:returns: quaternion [w, x, y , z]
'''
quat = Quaternion(e)
return quat.q
def rotate_quat(attitude, roll, pitch, yaw):
'''
Returns rotated quaternion
:param attitude: quaternion [w, x, y , z]
:param roll: rotation in rad
:param pitch: rotation in rad
:param yaw: rotation in rad
:returns: quaternion [w, x, y , z]
'''
quat = Quaternion(attitude)
rotation = Quaternion([roll, pitch, yaw])
res = rotation * quat
return res.q
def qroll(MSG):
'''return quaternion roll in degrees'''
q = Quaternion([MSG.Q1,MSG.Q2,MSG.Q3,MSG.Q4])
return degrees(q.euler[0])
def qpitch(MSG):
'''return quaternion pitch in degrees'''
q = Quaternion([MSG.Q1,MSG.Q2,MSG.Q3,MSG.Q4])
return degrees(q.euler[1])
def qyaw(MSG):
'''return quaternion yaw in degrees'''
q = Quaternion([MSG.Q1,MSG.Q2,MSG.Q3,MSG.Q4])
return degrees(q.euler[2])
def euler_rotated(MSG, roll, pitch, yaw):
'''return eulers in radians from quaternion for view at given attitude in euler radians'''
rot_view = Matrix3()
rot_view.from_euler(roll, pitch, yaw)
q = Quaternion([MSG.Q1,MSG.Q2,MSG.Q3,MSG.Q4])
dcm = (rot_view * q.dcm.transposed()).transposed()
return dcm.to_euler()
def euler_p90(MSG):
'''return eulers in radians from quaternion for view at pitch 90'''
return euler_rotated(MSG, 0, radians(90), 0);
def qroll_p90(MSG):
'''return quaternion roll in degrees for view at pitch 90'''
return degrees(euler_p90(MSG)[0])
def qpitch_p90(MSG):
'''return quaternion roll in degrees for view at pitch 90'''
return degrees(euler_p90(MSG)[1])
def qyaw_p90(MSG):
'''return quaternion roll in degrees for view at pitch 90'''
return degrees(euler_p90(MSG)[2])
def rotation_df(ATT):
'''return the current DCM rotation matrix'''
r = Matrix3()
r.from_euler(radians(ATT.Roll), radians(ATT.Pitch), radians(ATT.Yaw))
return r
def rotation2(AHRS2):
'''return the current DCM rotation matrix'''
r = Matrix3()
r.from_euler(AHRS2.roll, AHRS2.pitch, AHRS2.yaw)
return r
def earth_accel2(RAW_IMU,ATTITUDE):
'''return earth frame acceleration vector from AHRS2'''
r = rotation2(ATTITUDE)
accel = Vector3(RAW_IMU.xacc, RAW_IMU.yacc, RAW_IMU.zacc) * 9.81 * 0.001
return r * accel
def earth_accel_df(IMU,ATT):
'''return earth frame acceleration vector from df log'''
r = rotation_df(ATT)
accel = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
return r * accel
def earth_accel2_df(IMU,IMU2,ATT):
'''return earth frame acceleration vector from df log'''
r = rotation_df(ATT)
accel1 = Vector3(IMU.AccX, IMU.AccY, IMU.AccZ)
accel2 = Vector3(IMU2.AccX, IMU2.AccY, IMU2.AccZ)
accel = 0.5 * (accel1 + accel2)
return r * accel
def gps_velocity_df(GPS):
'''return GPS velocity vector'''
vx = GPS.Spd * cos(radians(GPS.GCrs))
vy = GPS.Spd * sin(radians(GPS.GCrs))
return Vector3(vx, vy, GPS.VZ)
def armed(HEARTBEAT):
'''return 1 if armed, 0 if not'''
from pymavlink import mavutil
if HEARTBEAT.type == mavutil.mavlink.MAV_TYPE_GCS:
self = mavutil.mavfile_global
if self.motors_armed():
return 1
return 0
if HEARTBEAT.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED:
return 1
return 0
def mode(HEARTBEAT):
'''return flight mode number'''
from pymavlink import mavutil
if HEARTBEAT.type == mavutil.mavlink.MAV_TYPE_GCS:
return None
return HEARTBEAT.custom_mode
'''
magnetic field tables for estimating earths mag field
updated 2019-12-20
'''
# set to the sampling in degrees for the table below
SAMPLING_RES = 10.0
SAMPLING_MIN_LAT = -90.0
SAMPLING_MAX_LAT = 90.0
SAMPLING_MIN_LON = -180.0
SAMPLING_MAX_LON = 180.0
declination_table = [
[148.83402,138.83401,128.83401,118.83402,108.83402,98.83402,88.83402,78.83402,68.83402,58.83402,48.83402,38.83402,28.83402,18.83402,8.83402,-1.16598,-11.16598,-21.16598,-31.16598,-41.16598,-51.16598,-61.16598,-71.16598,-81.16598,-91.16598,-101.16598,-111.16598,-121.16598,-131.16598,-141.16598,-151.16598,-161.16598,-171.16598,178.83402,168.83402,158.83402,148.83402],
[129.09306,116.89412,105.78898,95.63017,86.23504,77.42476,69.04348,60.96591,53.09841,45.37592,37.75568,30.20867,22.70998,15.23027,7.73037,0.16098,-7.53238,-15.40109,-23.48496,-31.80703,-40.37375,-49.18062,-58.22152,-67.49946,-77.03640,-86.88079,-97.11212,-107.84156,-119.20626,-131.35191,-144.39481,-158.35719,-173.08769,171.78274,156.78187,142.43310,129.09306],
[85.81367,77.83602,71.40003,65.88433,60.88263,56.08204,51.22755,46.12774,40.67419,34.85457,28.74909,22.50583,16.29363,10.23812,4.36029,-1.45095,-7.40778,-13.74178,-20.61213,-28.04922,-35.95530,-44.15322,-52.45486,-60.71951,-68.88732,-76.99183,-85.16746,-93.67473,-102.97950,-113.96429,-128.46293,-150.36073,175.55488,138.00554,112.28051,96.48319,85.81367],
[48.22805,46.81921,45.24300,43.71189,42.27245,40.80022,39.00177,36.49549,32.95972,28.26545,22.54379,16.18846,9.77818,3.88971,-1.16066,-5.50500,-9.68832,-14.40052,-20.13990,-26.99385,-34.63319,-42.50584,-50.08478,-57.00760,-63.07978,-68.20773,-72.30336,-75.14845,-76.14220,-73.56875,-61.46573,-19.66028,28.39616,43.98783,48.35027,49.03265,48.22805],
[31.42931,31.60530,31.28145,30.79047,30.38024,30.16777,29.97487,29.25597,27.27948,23.46202,17.66378,10.37176,2.68673,-4.06559,-9.01317,-12.19989,-14.47861,-17.04830,-21.00672,-26.83158,-33.98769,-41.32654,-47.85214,-52.97945,-56.36644,-57.72068,-56.62201,-52.29959,-43.64045,-30.08348,-13.41611,2.35315,14.41455,22.53506,27.54777,30.28028,31.42931],
[22.67985,23.21169,23.25494,22.99742,22.63545,22.42369,22.46877,22.43187,21.42571,18.33186,12.46440,4.21692,-4.75068,-12.30560,-17.22721,-19.74039,-20.77499,-21.16946,-22.14607,-25.39336,-31.01102,-37.19196,-42.26227,-45.34969,-45.95366,-43.77299,-38.67388,-30.72261,-20.87969,-11.16390,-2.84711,4.17466,10.15622,15.07307,18.83144,21.33469,22.67985],
[17.07334,17.59062,17.77934,17.69577,17.34929,16.88674,16.56217,16.38656,15.63204,12.86517,6.93562,-1.75751,-10.98436,-18.20308,-22.41588,-24.27993,-24.67590,-23.50719,-21.00605,-19.72369,-21.87960,-26.21214,-30.23195,-32.25069,-31.49417,-28.07385,-22.58093,-15.67611,-8.73630,-3.38117,0.43868,3.90117,7.49078,10.92679,13.85484,15.93237,17.07334],
[13.37426,13.67217,13.78744,13.78998,13.54222,12.98780,12.38852,11.96065,11.09372,8.25499,2.21162,-6.34723,-14.82596,-20.89032,-23.89434,-24.45752,-23.26867,-20.14461,-15.21492,-10.62227,-9.09952,-11.18829,-14.91638,-17.58833,-17.75073,-15.65321,-12.03643,-7.38772,-2.92537,-0.15156,1.30857,3.04660,5.56513,8.30463,10.76538,12.51642,13.37426],
[11.10969,11.11944,11.01236,10.99728,10.84097,10.32286,9.70494,9.19687,8.09157,4.94277,-1.12864,-9.05484,-16.32980,-21.04376,-22.55109,-21.19496,-17.89373,-13.44411,-8.58084,-4.24867,-1.62827,-1.86509,-4.55045,-7.42469,-8.64775,-8.12536,-6.32716,-3.48850,-0.64489,0.72027,0.97976,1.90952,4.01623,6.51266,8.80749,10.44185,11.10969],
[9.88349,9.72040,9.41289,9.38439,9.33291,8.89921,8.31537,7.68011,6.17272,2.60695,-3.33977,-10.37899,-16.38823,-19.72660,-19.70115,-16.80477,-12.41185,-7.91607,-4.15177,-1.10381,1.22204,1.82879,0.23517,-2.13784,-3.62813,-3.94630,-3.34494,-1.85961,-0.22644,0.26762,-0.08803,0.44077,2.38763,4.89878,7.32401,9.15291,9.88349],
[9.12464,9.18177,8.94457,9.04553,9.19618,8.89744,8.21223,7.14260,4.96349,0.86055,-4.97931,-11.14357,-15.85640,-17.78532,-16.57645,-13.08478,-8.67898,-4.59243,-1.60427,0.55144,2.39028,3.23916,2.31654,0.46434,-0.91732,-1.53003,-1.62589,-1.18564,-0.62403,-0.86624,-1.66562,-1.50145,0.21812,2.79294,5.56329,7.91823,9.12464],
[8.06290,8.93172,9.29767,9.81684,10.30693,10.20311,9.30721,7.53634,4.41412,-0.45099,-6.41113,-11.85029,-15.25565,-15.88209,-13.96742,-10.49677,-6.46933,-2.75504,-0.08743,1.66921,3.12874,3.96903,3.45981,2.05876,0.88578,0.20951,-0.29117,-0.68669,-1.18101,-2.27487,-3.63054,-3.95562,-2.62221,-0.08793,3.01927,6.01454,8.06290],
[6.31041,8.41617,9.93058,11.21483,12.13996,12.23034,11.14011,8.66524,4.49928,-1.34733,-7.71989,-12.71272,-15.08305,-14.75641,-12.45343,-9.09705,-5.35725,-1.83470,0.82823,2.57001,3.86519,4.69588,4.60243,3.76104,2.87601,2.13967,1.24833,0.02788,-1.62859,-3.80231,-5.90990,-6.79346,-5.87079,-3.45535,-0.15720,3.34006,6.31041],
[4.26294,7.59975,10.43666,12.70491,14.16020,14.43398,13.18770,10.10768,4.90586,-2.08386,-9.15208,-14.09196,-15.93051,-15.08268,-12.51517,-9.09089,-5.35759,-1.77919,1.14514,3.24292,4.77489,5.90082,6.46683,6.42854,5.95440,5.04613,3.49019,1.14215,-1.96091,-5.46021,-8.43365,-9.79880,-9.13330,-6.77311,-3.34752,0.50970,4.26294],
[2.57464,6.81988,10.72127,13.95370,16.10049,16.72460,15.39548,11.64478,5.15172,-3.36781,-11.49173,-16.72206,-18.40831,-17.31430,-14.50140,-10.79977,-6.74992,-2.76545,0.78143,3.70100,6.08013,8.07892,9.67963,10.67456,10.77058,9.65347,7.07600,3.02043,-2.09809,-7.27683,-11.16806,-12.81861,-12.13043,-9.62141,-5.97603,-1.77704,2.57464],
[1.43503,6.27921,10.88019,14.88261,17.80063,19.02158,17.77297,13.12636,4.53718,-6.54055,-16.27050,-21.85552,-23.31365,-21.85455,-18.61101,-14.36797,-9.64057,-4.79964,-0.13239,4.18385,8.10605,11.64519,14.70734,16.97990,17.92977,16.88820,13.23164,6.81146,-1.34665,-8.97876,-14.01257,-15.81005,-14.85926,-12.00033,-7.99733,-3.40265,1.43502],
[0.13094,5.45479,10.54962,15.08235,18.55436,20.17754,18.67574,12.20150,-0.36226,-15.49225,-26.46004,-31.19380,-31.33251,-28.66356,-24.33699,-19.03627,-13.18344,-7.06335,-0.88475,5.19490,11.05071,16.55252,21.50321,25.56424,28.16988,28.41286,24.95220,16.43585,3.63861,-8.67079,-16.18909,-18.69045,-17.68753,-14.54612,-10.18137,-5.16795,0.13094],
[-4.14830,1.12345,5.96040,9.84415,11.94596,10.80871,4.02869,-10.36405,-27.94912,-39.79617,-44.16477,-43.57950,-40.12657,-34.99189,-28.83083,-22.02403,-14.80922,-7.34799,0.23881,7.85050,15.39197,22.75942,29.82156,36.38924,42.15868,46.59144,48.63821,46.09967,34.72041,12.13936,-8.98865,-18.65098,-20.47841,-18.40133,-14.39834,-9.45818,-4.14830],
[-169.79948,-159.79948,-149.79948,-139.79948,-129.79948,-119.79948,-109.79948,-99.79948,-89.79948,-79.79948,-69.79948,-59.79948,-49.79948,-39.79948,-29.79948,-19.79948,-9.79948,0.20052,10.20052,20.20052,30.20052,40.20052,50.20052,60.20052,70.20052,80.20052,90.20052,100.20052,110.20052,120.20052,130.20052,140.20052,150.20052,160.20052,170.20052,-179.79948,-169.79948]
]
inclination_table = [
[-72.02070,-72.02071,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02070,-72.02071,-72.02070,-72.02071],
[-78.23208,-77.46612,-76.54604,-75.51344,-74.40408,-73.25043,-72.08420,-70.93779,-69.84384,-68.83332,-67.93241,-67.15960,-66.52407,-66.02648,-65.66205,-65.42519,-65.31392,-65.33255,-65.49161,-65.80511,-66.28626,-66.94289,-67.77395,-68.76776,-69.90201,-71.14489,-72.45664,-73.79090,-75.09549,-76.31306,-77.38240,-78.24183,-78.83644,-79.12876,-79.10879,-78.79618,-78.23208],
[-80.80007,-78.97830,-77.14756,-75.29021,-73.38193,-71.40769,-69.37743,-67.33857,-65.37896,-63.61481,-62.16302,-61.10460,-60.45316,-60.14557,-60.06470,-60.08698,-60.13217,-60.19219,-60.32958,-60.65151,-61.27183,-62.27481,-63.69257,-65.50302,-67.64474,-70.03800,-72.60073,-75.25455,-77.92173,-80.51496,-82.91263,-84.88736,-85.95658,-85.63251,-84.30522,-82.60297,-80.80007],
[-77.45516,-75.43606,-73.49604,-71.57699,-69.59653,-67.45749,-65.08505,-62.48902,-59.81600,-57.35291,-55.46368,-54.46054,-54.44855,-55.23685,-56.40404,-57.49090,-58.18917,-58.42457,-58.34278,-58.24952,-58.51231,-59.42525,-61.10217,-63.46936,-66.34503,-69.53469,-72.88656,-76.29320,-79.66218,-82.88130,-85.73520,-87.38955,-86.36194,-84.15583,-81.83294,-79.58616,-77.45517],
[-71.58214,-69.61565,-67.71488,-65.86479,-64.01505,-62.04510,-59.77482,-57.07400,-54.02405,-51.03212,-48.80491,-48.09823,-49.26412,-51.92460,-55.16562,-58.07793,-60.10267,-61.01513,-60.84749,-59.97822,-59.15799,-59.18575,-60.45940,-62.86069,-65.97595,-69.36736,-72.71868,-75.80268,-78.35851,-80.06262,-80.67983,-80.25871,-79.08901,-77.46953,-75.59576,-73.59787,-71.58215],
[-64.39807,-62.40221,-60.40960,-58.42549,-56.48151,-54.55795,-52.49863,-50.03954,-47.03044,-43.80319,-41.39118,-41.16169,-43.78688,-48.51954,-53.83971,-58.60413,-62.35170,-64.81766,-65.60605,-64.62579,-62.64650,-61.08659,-61.05364,-62.68316,-65.34905,-68.24163,-70.78236,-72.62117,-73.51797,-73.52965,-73.02048,-72.24528,-71.21594,-69.88980,-68.25886,-66.38361,-64.39807],
[-55.02826,-52.87120,-50.69073,-48.44731,-46.19513,-44.04849,-42.00465,-39.74123,-36.79801,-33.33110,-30.77621,-31.26765,-35.68330,-42.58861,-49.78570,-56.05256,-61.22645,-65.24992,-67.52021,-67.43115,-65.26549,-62.35447,-60.45735,-60.49007,-61.96843,-63.81210,-65.26064,-65.90429,-65.53604,-64.51130,-63.52424,-62.77292,-61.94296,-60.76846,-59.16172,-57.17787,-55.02826],
[-42.23934,-39.72248,-37.30606,-34.84704,-32.29950,-29.84715,-27.62882,-25.25073,-22.02346,-18.13398,-15.56621,-17.09131,-23.52381,-32.78684,-42.11183,-49.91489,-55.93881,-60.34177,-62.85919,-63.04596,-60.93631,-57.41244,-54.21948,-52.82417,-53.10885,-54.02972,-54.81946,-54.92826,-53.94911,-52.38029,-51.27875,-50.79557,-50.20937,-49.05107,-47.23568,-44.84941,-42.23934],
[-25.31616,-22.26353,-19.64432,-17.13765,-14.48794,-11.90452,-9.56551,-6.93532,-3.35246,0.62534,2.69744,0.20162,-7.49683,-18.49333,-29.80260,-38.99555,-45.22087,-48.81124,-50.28975,-49.88122,-47.49942,-43.57896,-39.80201,-37.81589,-37.58213,-38.12733,-38.79073,-38.91636,-37.84560,-36.15384,-35.28930,-35.35164,-35.11620,-33.90633,-31.71407,-28.67497,-25.31616],
[-5.22365,-1.68002,0.98130,3.28459,5.73141,8.13291,10.33797,12.92226,16.28019,19.52644,20.64919,17.75076,10.26968,-0.68527,-12.39654,-21.81573,-27.55757,-29.98547,-30.24089,-29.18412,-26.62224,-22.51726,-18.50023,-16.36665,-16.02599,-16.47452,-17.18522,-17.56335,-16.82114,-15.49276,-15.21405,-16.04947,-16.38744,-15.33409,-12.92178,-9.31548,-5.22365],
[14.65727,18.24457,20.73560,22.68750,24.74090,26.83478,28.82717,31.06453,33.64927,35.73650,35.91607,33.06931,26.82267,17.82545,8.14938,0.35249,-4.22224,-5.65846,-5.08244,-3.67265,-1.30171,2.36894,6.00997,7.96495,8.31670,8.01335,7.45026,6.96852,7.19717,7.76654,7.31456,5.77666,4.68883,5.07867,7.05547,10.50432,14.65727],
[31.00203,34.03206,36.22486,37.93041,39.71929,41.67672,43.64451,45.63057,47.51149,48.64179,48.13476,45.46014,40.64785,34.34474,27.90343,22.77471,19.78522,19.08118,19.96093,21.39191,23.28877,25.96005,28.61507,30.09860,30.42073,30.29938,30.04062,29.74069,29.65970,29.53545,28.53162,26.62276,24.91817,24.37385,25.31100,27.72542,31.00203],
[43.33608,45.45851,47.28808,48.92364,50.69187,52.68920,54.75852,56.70372,58.24993,58.87748,58.03709,55.61656,52.01961,47.93440,44.13769,41.24459,39.61639,39.39065,40.22833,41.47069,42.89022,44.59331,46.23881,47.24171,47.57154,47.63935,47.66249,47.62919,47.50096,47.00810,45.71114,43.68014,41.65440,40.38784,40.27252,41.37670,43.33608],
[53.10131,54.36161,55.82606,57.45873,59.32146,61.39014,63.50628,65.43734,66.85905,67.31408,66.44780,64.38450,61.68268,58.96721,56.68844,55.08044,54.25566,54.24982,54.89128,55.82780,56.83438,57.87845,58.85920,59.59271,60.06042,60.40887,60.71534,60.90716,60.81057,60.15911,58.74748,56.73559,54.64618,53.02448,52.20550,52.28132,53.10131],
[61.90363,62.59082,63.73000,65.25974,67.10373,69.13404,71.16923,72.97878,74.24745,74.58969,73.79370,72.08739,69.99806,68.01780,66.44167,65.38391,64.86230,64.84287,65.21557,65.80372,66.46017,67.12970,67.80720,68.49179,69.18909,69.89166,70.52514,70.92481,70.86509,70.14456,68.73300,66.86155,64.92449,63.29260,62.20331,61.74779,61.90363],
[70.57319,70.97549,71.81891,73.05700,74.60513,76.33503,78.07584,79.60158,80.60606,80.76720,79.99878,78.58008,76.92858,75.37566,74.11340,73.22087,72.70518,72.52979,72.62677,72.91213,73.31657,73.81370,74.42101,75.17239,76.07666,77.07532,78.01176,78.63384,78.66938,77.98598,76.70086,75.10445,73.50638,72.14451,71.16426,70.63412,70.57319],
[78.82351,79.06440,79.60559,80.41612,81.44382,82.60991,83.79747,84.82323,85.41014,85.29919,84.52961,83.39228,82.15604,80.99185,79.99787,79.22370,78.68527,78.37555,78.27394,78.35635,78.60546,79.01788,79.60326,80.37283,81.31875,82.38760,83.44917,84.26774,84.54142,84.12292,83.18704,82.03917,80.91872,79.97342,79.28584,78.89722,78.82351],
[85.92404,85.99807,86.21370,86.55320,86.98729,87.46743,87.90712,88.15645,88.05609,87.61718,86.98684,86.28678,85.58976,84.94098,84.37090,83.90045,83.54357,83.30879,83.20077,83.22179,83.37274,83.65354,84.06234,84.59369,85.23581,85.96691,86.74942,87.51716,88.14109,88.39226,88.15089,87.63861,87.08412,86.59723,86.22810,86.00055,85.92404],
[88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420,88.21420]
]
intensity_table = [
[0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507,0.54507],
[0.60562,0.59923,0.59133,0.58213,0.57184,0.56070,0.54892,0.53679,0.52458,0.51262,0.50121,0.49067,0.48128,0.47330,0.46697,0.46249,0.46006,0.45986,0.46203,0.46665,0.47372,0.48312,0.49460,0.50778,0.52217,0.53717,0.55217,0.56652,0.57964,0.59102,0.60027,0.60715,0.61152,0.61342,0.61294,0.61026,0.60562],
[0.62993,0.61663,0.60163,0.58513,0.56721,0.54792,0.52741,0.50597,0.48417,0.46274,0.44252,0.42424,0.40840,0.39524,0.38483,0.37722,0.37262,0.37149,0.37444,0.38211,0.39496,0.41303,0.43591,0.46271,0.49220,0.52288,0.55321,0.58160,0.60660,0.62705,0.64217,0.65166,0.65569,0.65477,0.64966,0.64113,0.62993],
[0.61859,0.59954,0.57951,0.55859,0.53652,0.51281,0.48703,0.45921,0.43010,0.40125,0.37460,0.35184,0.33383,0.32034,0.31043,0.30313,0.29816,0.29624,0.29887,0.30789,0.32478,0.35003,0.38290,0.42162,0.46385,0.50716,0.54918,0.58755,0.62009,0.64502,0.66138,0.66918,0.66921,0.66276,0.65127,0.63614,0.61859],
[0.58434,0.56134,0.53820,0.51511,0.49174,0.46715,0.44010,0.40984,0.37699,0.34382,0.31364,0.28967,0.27359,0.26453,0.25966,0.25610,0.25262,0.24997,0.25053,0.25779,0.27518,0.30435,0.34417,0.39141,0.44206,0.49253,0.54000,0.58194,0.61593,0.64015,0.65395,0.65794,0.65353,0.64245,0.62626,0.60641,0.58434],
[0.53921,0.51448,0.48995,0.46595,0.44250,0.41888,0.39359,0.36521,0.33366,0.30097,0.27115,0.24895,0.23726,0.23477,0.23677,0.23885,0.23920,0.23803,0.23691,0.23986,0.25296,0.28060,0.32245,0.37391,0.42855,0.48085,0.52753,0.56643,0.59562,0.61429,0.62316,0.62341,0.61626,0.60311,0.58508,0.56327,0.53921],
[0.48785,0.46379,0.43988,0.41636,0.39367,0.37182,0.34998,0.32674,0.30099,0.27346,0.24780,0.22959,0.22251,0.22507,0.23198,0.23916,0.24558,0.25057,0.25278,0.25389,0.26060,0.28058,0.31692,0.36595,0.41928,0.46891,0.51065,0.54231,0.56238,0.57214,0.57493,0.57236,0.56426,0.55095,0.53309,0.51149,0.48785],
[0.43209,0.41089,0.38995,0.36941,0.34979,0.33155,0.31470,0.29826,0.28044,0.26067,0.24164,0.22828,0.22418,0.22862,0.23770,0.24867,0.26122,0.27397,0.28296,0.28650,0.28895,0.29863,0.32271,0.36133,0.40653,0.44908,0.48374,0.50731,0.51774,0.51826,0.51530,0.51075,0.50252,0.48985,0.47321,0.45336,0.43209],
[0.37890,0.36283,0.34739,0.33271,0.31922,0.30724,0.29696,0.28780,0.27809,0.26658,0.25432,0.24439,0.23994,0.24247,0.25117,0.26398,0.27930,0.29517,0.30771,0.31403,0.31546,0.31823,0.33068,0.35590,0.38846,0.42051,0.44683,0.46322,0.46680,0.46142,0.45465,0.44832,0.43950,0.42721,0.41228,0.39568,0.37890],
[0.34114,0.33192,0.32348,0.31616,0.31061,0.30672,0.30410,0.30225,0.29980,0.29492,0.28700,0.27755,0.26956,0.26653,0.27083,0.28123,0.29446,0.30791,0.31935,0.32678,0.32987,0.33216,0.33985,0.35562,0.37648,0.39779,0.41572,0.42632,0.42691,0.42012,0.41134,0.40209,0.39087,0.37775,0.36429,0.35181,0.34114],
[0.32818,0.32516,0.32319,0.32283,0.32517,0.32980,0.33535,0.34062,0.34397,0.34296,0.33615,0.32475,0.31211,0.30265,0.30010,0.30443,0.31265,0.32241,0.33228,0.34067,0.34700,0.35327,0.36224,0.37416,0.38773,0.40158,0.41356,0.42060,0.42076,0.41455,0.40362,0.38928,0.37303,0.35700,0.34334,0.33369,0.32818],
[0.33981,0.34000,0.34266,0.34809,0.35721,0.36937,0.38240,0.39400,0.40180,0.40293,0.39568,0.38157,0.36502,0.35110,0.34343,0.34233,0.34605,0.35337,0.36304,0.37282,0.38187,0.39163,0.40273,0.41394,0.42487,0.43601,0.44625,0.45294,0.45399,0.44803,0.43420,0.41393,0.39124,0.37028,0.35389,0.34377,0.33981],
[0.37236,0.37289,0.37826,0.38816,0.40237,0.41956,0.43724,0.45274,0.46333,0.46592,0.45864,0.44316,0.42447,0.40808,0.39741,0.39283,0.39339,0.39860,0.40735,0.41725,0.42701,0.43745,0.44902,0.46095,0.47308,0.48570,0.49764,0.50631,0.50895,0.50305,0.48719,0.46316,0.43591,0.41060,0.39068,0.37789,0.37236],
[0.42245,0.42215,0.42846,0.44064,0.45724,0.47601,0.49435,0.50993,0.52038,0.52304,0.51631,0.50148,0.48288,0.46550,0.45264,0.44515,0.44269,0.44488,0.45083,0.45875,0.46742,0.47714,0.48867,0.50225,0.51759,0.53373,0.54863,0.55948,0.56339,0.55795,0.54232,0.51859,0.49131,0.46537,0.44426,0.42977,0.42245],
[0.48319,0.48226,0.48758,0.49842,0.51308,0.52913,0.54416,0.55622,0.56358,0.56459,0.55836,0.54575,0.52954,0.51323,0.49956,0.48985,0.48445,0.48324,0.48569,0.49085,0.49796,0.50710,0.51894,0.53390,0.55144,0.56981,0.58634,0.59810,0.60259,0.59827,0.58527,0.56571,0.54318,0.52144,0.50331,0.49032,0.48319],
[0.53929,0.53799,0.54070,0.54691,0.55552,0.56497,0.57363,0.58010,0.58328,0.58236,0.57702,0.56775,0.55586,0.54317,0.53137,0.52173,0.51505,0.51167,0.51159,0.51458,0.52039,0.52900,0.54059,0.55506,0.57159,0.58845,0.60334,0.61392,0.61846,0.61626,0.60790,0.59512,0.58032,0.56589,0.55363,0.54459,0.53929],
[0.57278,0.57090,0.57068,0.57187,0.57403,0.57656,0.57878,0.58007,0.57991,0.57795,0.57409,0.56849,0.56162,0.55414,0.54681,0.54040,0.53554,0.53271,0.53223,0.53423,0.53873,0.54564,0.55475,0.56559,0.57735,0.58891,0.59900,0.60643,0.61040,0.61066,0.60758,0.60203,0.59514,0.58801,0.58156,0.57637,0.57278],
[0.57900,0.57728,0.57584,0.57463,0.57360,0.57263,0.57160,0.57039,0.56890,0.56707,0.56489,0.56241,0.55976,0.55710,0.55465,0.55263,0.55127,0.55074,0.55121,0.55273,0.55530,0.55883,0.56314,0.56798,0.57300,0.57785,0.58216,0.58562,0.58802,0.58928,0.58943,0.58865,0.58716,0.58523,0.58309,0.58097,0.57900],
[0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830,0.56830]
]
def interpolate_table(table, latitude_deg, longitude_deg):
'''interpolate inside a table for a given lat/lon in degrees'''
# round down to nearest sampling resolution
min_lat = int(floor(latitude_deg / SAMPLING_RES) * SAMPLING_RES)
min_lon = int(floor(longitude_deg / SAMPLING_RES) * SAMPLING_RES)
# find index of nearest low sampling point
min_lat_index = int(floor(-(SAMPLING_MIN_LAT) + min_lat) / SAMPLING_RES)
min_lon_index = int(floor(-(SAMPLING_MIN_LON) + min_lon) / SAMPLING_RES)
# calculate intensity
data_sw = table[min_lat_index][min_lon_index]
data_se = table[min_lat_index][min_lon_index + 1]
data_ne = table[min_lat_index + 1][min_lon_index + 1]
data_nw = table[min_lat_index + 1][min_lon_index]
# perform bilinear interpolation on the four grid corners
data_min = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_se - data_sw) + data_sw
data_max = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_ne - data_nw) + data_nw
value = ((latitude_deg - min_lat) / SAMPLING_RES) * (data_max - data_min) + data_min
return value
earth_declination = None
'''
calculate magnetic field intensity and orientation, interpolating in tables
returns array [declination_deg, inclination_deg, intensity] or None
'''
def get_mag_field_ef(latitude_deg, longitude_deg):
# limit to table bounds
if latitude_deg < SAMPLING_MIN_LAT:
return None
if latitude_deg >= SAMPLING_MAX_LAT:
return None
if longitude_deg < SAMPLING_MIN_LON:
return None
if longitude_deg >= SAMPLING_MAX_LON:
return None
intensity_gauss = interpolate_table(intensity_table, latitude_deg, longitude_deg)
declination_deg = interpolate_table(declination_table, latitude_deg, longitude_deg)
inclination_deg = interpolate_table(inclination_table, latitude_deg, longitude_deg)
global earth_declination
earth_declination = declination_deg
return [declination_deg, inclination_deg, intensity_gauss]
def expected_earth_field_lat_lon(lat, lon):
'''return expected magnetic field for a location'''
field_var = get_mag_field_ef(lat, lon)
mag_ef = Vector3(field_var[2]*1000.0, 0.0, 0.0)
R = Matrix3()
R.from_euler(0.0, -radians(field_var[1]), radians(field_var[0]))
mag_ef = R * mag_ef
earth_field = mag_ef
return earth_field
def get_lat_lon_status(GPS):
'''get lat, lon and status from a GPS message'''
if hasattr(GPS,'fix_type'):
gps_status = GPS.fix_type
lat = GPS.lat*1.0e-7
lon = GPS.lon*1.0e-7
else:
gps_status = GPS.Status
lat = GPS.Lat
lon = GPS.Lng
return (lat, lon, gps_status)
def expected_earth_field(GPS):
'''return expected magnetic field for a location'''
(lat, lon, gps_status) = get_lat_lon_status(GPS)
if gps_status < 3:
return Vector3(0,0,0)
return expected_earth_field_lat_lon(lat, lon)
def expected_mag(GPS,ATT,roll_adjust=0,pitch_adjust=0,yaw_adjust=0):
'''return expected magnetic field for a location and attitude'''
earth_field = expected_earth_field(GPS)
if earth_field is None:
return Vector3(0,0,0)
if hasattr(ATT,'roll'):
roll = degrees(ATT.roll)+roll_adjust
pitch = degrees(ATT.pitch)+pitch_adjust
yaw = degrees(ATT.yaw)+yaw_adjust
else:
roll = ATT.Roll+roll_adjust
pitch = ATT.Pitch+pitch_adjust
yaw = ATT.Yaw+yaw_adjust
rot = Matrix3()
rot.from_euler(radians(roll), radians(pitch), radians(yaw))
field = rot.transposed() * earth_field
return field
def expected_mag_latlon(lat,lon,ATT,roll_adjust=0,pitch_adjust=0,yaw_adjust=0):
'''return expected magnetic field for a location and attitude'''
earth_field = expected_earth_field_lat_lon(lat,lon)
if earth_field is None:
return Vector3(0,0,0)
if hasattr(ATT,'roll'):
roll = degrees(ATT.roll)+roll_adjust
pitch = degrees(ATT.pitch)+pitch_adjust
yaw = degrees(ATT.yaw)+yaw_adjust
else:
roll = ATT.Roll+roll_adjust
pitch = ATT.Pitch+pitch_adjust
yaw = ATT.Yaw+yaw_adjust
rot = Matrix3()
rot.from_euler(radians(roll), radians(pitch), radians(yaw))
field = rot.transposed() * earth_field
return field
def mag_yaw(GPS,ATT,MAG):
'''calculate heading from raw magnetometer'''
ef = expected_earth_field(GPS)
mag_x = MAG.MagX
mag_y = MAG.MagY
mag_z = MAG.MagZ
# go via a DCM matrix to match the APM calculation
dcm_matrix = rotation_df(ATT)
cos_pitch_sq = 1.0-(dcm_matrix.c.x*dcm_matrix.c.x)
headY = mag_y * dcm_matrix.c.z - mag_z * dcm_matrix.c.y
headX = mag_x * cos_pitch_sq - dcm_matrix.c.x * (mag_y * dcm_matrix.c.y + mag_z * dcm_matrix.c.z)
# we need the declination too
(lat, lon, gps_status) = get_lat_lon_status(GPS)
field = get_mag_field_ef(lat, lon)
declination = field[0]
heading = degrees(atan2(-headY,headX)) + declination
if heading < 0:
heading += 360
return heading
def expected_mag_yaw(GPS,ATT,MAG,roll_adjust=0,pitch_adjust=0,yaw_adjust=0):
'''return expected magnetic field for a location and attitude'''
earth_field = expected_earth_field(GPS)
roll = ATT.Roll+roll_adjust
pitch = ATT.Pitch+pitch_adjust
yaw = mag_yaw(GPS,ATT,MAG)
rot = Matrix3()
rot.from_euler(radians(roll), radians(pitch), radians(yaw))
field = rot.transposed() * earth_field
return field
def earth_field_error(GPS,NKF2):
'''return vector error in earth field estimate'''
earth_field = expected_earth_field(GPS)
if earth_field is None:
return Vector3(0,0,0)
ef = Vector3(NKF2.MN,NKF2.ME,NKF2.MD)
ret = ef - earth_field
return ret
def distance_home_df(GPS,ORGN):
'''distance from home origin'''
return distance_two(GPS_RAW, first_fix)
def airspeed_estimate(GLOBAL_POSITION_INT,WIND):
'''estimate airspeed'''
wind = WIND
gpi = GLOBAL_POSITION_INT
from pymavlink.rotmat import Vector3
import math
wind3d = Vector3(wind.speed*math.cos(math.radians(wind.direction)),
wind.speed*math.sin(math.radians(wind.direction)), 0)
ground = Vector3(gpi.vx*0.01, gpi.vy*0.01, 0)
airspeed = (ground + wind3d).length()
return airspeed
def distance_from(GPS_RAW1, lat, lon):
'''
Return the distance from a given location in meters
Calculated as the great-circle distance using 'haversine’ formula
(Ref: http://www.movable-type.co.uk/scripts/latlong.html)
Uses the globally-average earth radius value of 6371km
'''
if hasattr(GPS_RAW1, 'Lat'):
lat1 = radians(GPS_RAW1.Lat)
lon1 = radians(GPS_RAW1.Lng)
elif hasattr(GPS_RAW1, 'cog'):
lat1 = radians(GPS_RAW1.lat)*1.0e-7
lon1 = radians(GPS_RAW1.lon)*1.0e-7
else:
lat1 = radians(GPS_RAW1.lat)
lon1 = radians(GPS_RAW1.lon)
lat2 = radians(lat)
lon2 = radians(lon)
dLat = lat2 - lat1
dLon = lon2 - lon1
a = sin(0.5*dLat)**2 + sin(0.5*dLon)**2 * cos(lat1) * cos(lat2)
c = 2.0 * atan2(sqrt(a), sqrt(1.0-a))
ground_dist = 6371 * 1000 * c
return ground_dist
def distance_lat_lon(lat1, lon1, lat2, lon2):
'''
Return the distance between two points in metres
Calculated as the great-circle distance using 'haversine’ formula
(Ref: http://www.movable-type.co.uk/scripts/latlong.html)
Uses the globally-average earth radius value of 6371km
'''
lat1 = radians(lat1)
lon1 = radians(lon1)
lat2 = radians(lat2)
lon2 = radians(lon2)
dLat = lat2 - lat1
dLon = lon2 - lon1
a = sin(0.5*dLat)**2 + sin(0.5*dLon)**2 * cos(lat1) * cos(lat2)
c = 2.0 * atan2(sqrt(a), sqrt(1.0-a))
ground_dist = 6371 * 1000 * c
return ground_dist
def constrain(v, minv, maxv):
if v < minv:
v = minv
if v > maxv:
v = maxv
return v
def sim_body_rates(SIM):
'''return body frame rates from simulator attitudes'''
rollRate = delta(SIM.Roll,'sbr',SIM.TimeUS)
pitchRate = delta(SIM.Pitch,'sbp',SIM.TimeUS)
yawRate = delta(SIM.Yaw,'sby',SIM.TimeUS)
phi = radians(SIM.Roll)
theta = radians(SIM.Pitch)
phiDot = radians(rollRate)
thetaDot = radians(pitchRate)
psiDot = radians(yawRate)
p = phiDot - psiDot*sin(theta)
q = cos(phi)*thetaDot + sin(phi)*psiDot*cos(theta)
r = cos(phi)*psiDot*cos(theta) - sin(phi)*thetaDot
return Vector3(p, q, r)
def reset_state_data():
'''reset state data, used on log rewind'''
global average_data
global derivative_data
global lowpass_data
global last_diff
global last_delta
global first_fix
global dcm_state
global last_sum
global last_integral
average_data.clear()
derivative_data.clear()
lowpass_data.clear()
last_delta.clear()
last_sum.clear()
last_integral.clear()
first_fix = None
dcm_state = None
# terrain functions, using MAVProxy elevation module
EleModel = None
def terrain_height(lat,lon):
'''get terrain height'''
global EleModel
if EleModel is None:
from MAVProxy.modules.mavproxy_map.mp_elevation import ElevationModel
EleModel = ElevationModel("srtm",offline=1)
return EleModel.GetElevation(lat,lon)
def terrain_margin_lat_lon(lat1,lon1,alt1,lat2,lon2,alt2):
'''
return minimum height above terrain on path between two positions (AMSL)
'''
distance = distance_lat_lon(lat1, lon1, lat2, lon2)
steps = distance / 10
dlat = (lat2-lat1) / steps
dlon = (lon2-lon1) / steps
dalt = (alt2-alt1) / steps
min_margin = None
for i in range(max(1,int(steps))):
talt = terrain_height(lat1,lon1)
margin = alt1 - talt
if min_margin is None or margin < min_margin:
min_margin = margin
lat1 += dlat
lon1 += dlon
alt1 += dalt
return min_margin
def terrain_margin(TERR,lat,lon,antenna_height):
'''
return minimum height above terrain on path between two positions (AMSL)
'''
alt = terrain_height(lat,lon)+antenna_height
return terrain_margin_lat_lon(TERR.Lat,TERR.Lng,TERR.CHeight+TERR.TerrH,lat,lon,alt)
def radio_margin(TERR,lat,lon,antenna_height):
'''
return how much height we could lose and still have line of sight from an antenna at antenna_height
above ground at lat/lon
'''
ant_alt = terrain_height(lat,lon)+antenna_height
if hasattr(TERR,'CHeight'):
alt = TERR.CHeight+TERR.TerrH
else:
# allow for GPS messages
alt = TERR.Alt
low = -alt
high = 0
while high > low+1:
test = 0.5*(low+high)
m = terrain_margin_lat_lon(TERR.Lat,TERR.Lng,alt+test,lat,lon,ant_alt)
if m > 0:
high = test
elif m < 0:
low = test
else:
low = test
high = test
return -high
def mm_curr(RCOU,BAT,PWM_MIN,PWM_MAX,Mfirst,Mlast):
'''
motor model to predict current draw given PWM to VTOL motors
returned value should be proportional to expected total current draw
'''
total_curr = 0.0
voltage = BAT.Volt
for m in range(Mfirst,Mlast+1):
pwm = getattr(RCOU,'C%u'%m,None)
if pwm is None:
return 0.0
command = voltage*max(pwm - PWM_MIN,0)/(PWM_MAX-PWM_MIN)
total_curr += command**2
return total_curr
def RotateMag(MAG,rotation):
'''rotate a MAG message by rotation enumeration'''
v = Vector3(MAG.MagX,MAG.MagY,MAG.MagZ)
return v.rotate_by_id(rotation)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。