flightdata 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flightdata-0.2.1 → flightdata-0.2.3}/PKG-INFO +4 -1
- flightdata-0.2.3/flightdata/__init__.py +8 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/__init__.py +1 -1
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/collection.py +4 -4
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/table.py +74 -42
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/environment/environment.py +2 -2
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/flight.py +3 -4
- {flightdata-0.2.1/flightdata/model → flightdata-0.2.3/flightdata}/flow.py +2 -2
- flightdata-0.2.3/flightdata/model/__init__.py +11 -0
- flightdata-0.2.3/flightdata/model/aerodynamic.py +33 -0
- flightdata-0.2.3/flightdata/model/thrust.py +10 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/origin.py +1 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/state.py +52 -47
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/PKG-INFO +4 -1
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/SOURCES.txt +4 -3
- {flightdata-0.2.1 → flightdata-0.2.3}/setup.cfg +1 -1
- flightdata-0.2.1/flightdata/__init__.py +0 -6
- flightdata-0.2.1/flightdata/model/__init__.py +0 -3
- flightdata-0.2.1/flightdata/model/constants.py +0 -19
- {flightdata-0.2.1 → flightdata-0.2.3}/LICENSE +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/README.md +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/constructs.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/numpy_encoder.py +0 -0
- {flightdata-0.2.1/flightdata/model → flightdata-0.2.3/flightdata}/coefficients.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/environment/__init__.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/environment/wind.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/__init__.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/ardupilot.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/fields.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/dependency_links.txt +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/requires.txt +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/top_level.txt +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/setup.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/test/test_fields.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/test/test_flight.py +0 -0
- {flightdata-0.2.1 → flightdata-0.2.3}/test/test_origin.py +0 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flightdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Module for handling UAV flight log data
|
|
5
5
|
Home-page: https://github.com/PyFlightCoach/FlightData
|
|
6
6
|
Author: Thomas David
|
|
7
7
|
Author-email: thomasdavid0@gmail.com
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: pfc-geometry
|
|
10
13
|
|
|
11
14
|
## FlightData
|
|
12
15
|
This repo is contains a set of datastructures and tools for handling flight log data.
|
|
@@ -48,11 +48,11 @@ class Collection:
|
|
|
48
48
|
def to_list(self) -> List[T]:
|
|
49
49
|
return list(self.data.values())
|
|
50
50
|
|
|
51
|
-
def to_dicts(self) -> list[dict[str, Any]]:
|
|
52
|
-
return [v.to_dict() for v in self.data.values()]
|
|
51
|
+
def to_dicts(self, *args, **kwargs) -> list[dict[str, Any]]:
|
|
52
|
+
return [v.to_dict(*args, **kwargs) for v in self.data.values()]
|
|
53
53
|
|
|
54
|
-
def to_dict(self) -> dict[str, dict[str, Any]]:
|
|
55
|
-
return {k: v.to_dict() for k, v in self.data.items()}
|
|
54
|
+
def to_dict(self, *args, **kwargs) -> dict[str, dict[str, Any]]:
|
|
55
|
+
return {k: v.to_dict(*args, **kwargs) for k, v in self.data.items()}
|
|
56
56
|
|
|
57
57
|
@classmethod
|
|
58
58
|
def from_dicts(cls, vals: list[dict[str: Any]]) -> Self:
|
|
@@ -2,51 +2,20 @@ from __future__ import annotations
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import numpy.typing as npt
|
|
5
|
-
from geometry import Base,
|
|
6
|
-
from typing import Union,
|
|
5
|
+
from geometry import Base, Time
|
|
6
|
+
from typing import Union, Self, Tuple
|
|
7
7
|
from .constructs import SVar, Constructs
|
|
8
8
|
from numbers import Number
|
|
9
|
-
|
|
10
9
|
from time import time
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
class Time(Base):
|
|
14
|
-
cols=["t", "dt"]
|
|
15
|
-
|
|
16
|
-
@staticmethod
|
|
17
|
-
def from_t(t: np.ndarray) -> Self:
|
|
18
|
-
if isinstance(t, Number):
|
|
19
|
-
return Time(t, 1/30)
|
|
20
|
-
else:
|
|
21
|
-
dt = np.array([1/30]) if len(t) == 1 else np.gradient(t)
|
|
22
|
-
return Time(t, dt)
|
|
23
|
-
|
|
24
|
-
def scale(self, duration) -> Self:
|
|
25
|
-
old_duration = self.t[-1] - self.t[0]
|
|
26
|
-
sfac = duration / old_duration
|
|
27
|
-
return Time(
|
|
28
|
-
self.t[0] + (self.t - self.t[0]) * sfac,
|
|
29
|
-
self.dt * sfac
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
def reset_zero(self):
|
|
33
|
-
return Time(self.t - self.t[0], self.dt)
|
|
34
|
-
|
|
35
|
-
@staticmethod
|
|
36
|
-
def now():
|
|
37
|
-
return Time.from_t(time())
|
|
38
|
-
|
|
39
|
-
def extend(self):
|
|
40
|
-
return Time.concatenate([self, Time(self.t[-1] + self.dt[-1], self.dt[-1])])
|
|
41
|
-
|
|
42
|
-
|
|
43
12
|
def make_time(tab):
|
|
44
13
|
return Time.from_t(tab.t)
|
|
45
14
|
|
|
46
15
|
|
|
47
16
|
class Table:
|
|
48
17
|
constructs = Constructs([
|
|
49
|
-
|
|
18
|
+
SVar("time", Time, ["t", "dt"] , make_time )
|
|
50
19
|
])
|
|
51
20
|
|
|
52
21
|
def __init__(self, data: pd.DataFrame, fill=True, min_len=1):
|
|
@@ -94,6 +63,8 @@ class Table:
|
|
|
94
63
|
|
|
95
64
|
@classmethod
|
|
96
65
|
def from_dict(Cls, data):
|
|
66
|
+
if ['data'] in data:
|
|
67
|
+
data = data['data']
|
|
97
68
|
return Cls(pd.DataFrame.from_dict(data).set_index("t", drop=False))
|
|
98
69
|
|
|
99
70
|
def __len__(self):
|
|
@@ -118,7 +89,31 @@ class Table:
|
|
|
118
89
|
inds = self.data.reset_index(names="t2").set_index("t").loc[sli].t2.to_numpy()#set_index("t", drop=False).columns
|
|
119
90
|
|
|
120
91
|
return self.__class__(self.data.loc[inds])
|
|
121
|
-
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def stack(Cls, sections: list, overlap: int=1) -> Self:
|
|
95
|
+
"""Stack a list of Tables on top of each other.
|
|
96
|
+
The overlap is the number of rows to overlap between each section
|
|
97
|
+
"""
|
|
98
|
+
# first build list of index offsets, to be added to each dataframe
|
|
99
|
+
if overlap > 0:
|
|
100
|
+
offsets = np.cumsum([0] + [sec.data.index[-overlap] for sec in sections[:-1]])
|
|
101
|
+
dfs = [section.data.iloc[:-overlap] for section in sections[:-1]] + [sections[-1].data]
|
|
102
|
+
elif overlap == 0:
|
|
103
|
+
offsets = np.cumsum([0] + [sec.duration + sec.dt[-1] for sec in sections[:-1]])
|
|
104
|
+
dfs = [section.data for section in sections]
|
|
105
|
+
else:
|
|
106
|
+
raise AttributeError("Overlap must be >= 0")
|
|
107
|
+
|
|
108
|
+
for df, offset in zip(dfs, offsets):
|
|
109
|
+
df.index = np.array(df.index) - df.index[0] + offset
|
|
110
|
+
combo = pd.concat(dfs)
|
|
111
|
+
combo.index.name = "t"
|
|
112
|
+
|
|
113
|
+
combo["t"] = combo.index
|
|
114
|
+
|
|
115
|
+
return Cls(combo)
|
|
116
|
+
|
|
122
117
|
def __iter__(self):
|
|
123
118
|
for ind in list(self.data.index):
|
|
124
119
|
yield self[ind]
|
|
@@ -169,6 +164,9 @@ class Table:
|
|
|
169
164
|
ignore_index=True
|
|
170
165
|
).set_index("t", drop=False))
|
|
171
166
|
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
172
170
|
def label(self, **kwargs) -> Self:
|
|
173
171
|
return self.__class__(self.data.assign(**kwargs))
|
|
174
172
|
|
|
@@ -239,19 +237,51 @@ class Table:
|
|
|
239
237
|
def shift_label(self, offset: int, min_len=None, **kwargs) -> Self:
|
|
240
238
|
'''Shift the end of a label forwards or backwards by offset rows
|
|
241
239
|
Do not allow a label to be reduced to less than min_len'''
|
|
240
|
+
if min_len is None:
|
|
241
|
+
min_len=1
|
|
242
242
|
ranges = self.label_ranges()
|
|
243
243
|
i = self.get_label_id(**kwargs)
|
|
244
244
|
labels: pd.DataFrame = self.labels.copy()
|
|
245
|
+
labcols = [labels.columns.get_loc(c) for c in kwargs.keys()]
|
|
245
246
|
if offset > 0 and i < len(ranges):
|
|
246
|
-
offset = min(offset,
|
|
247
|
+
offset = min(offset, ranges.iloc[i+1, -1] - min_len)
|
|
247
248
|
if offset > 0:
|
|
248
|
-
labels.iloc[ranges.iloc[i+1].start:ranges.iloc[i+1].start+offset,
|
|
249
|
-
elif offset < 0
|
|
250
|
-
offset = max(offset, -
|
|
249
|
+
labels.iloc[ranges.iloc[i+1].start:ranges.iloc[i+1].start+offset, labcols] = pd.Series(kwargs)
|
|
250
|
+
elif offset < 0:
|
|
251
|
+
offset = max(offset, -ranges.iloc[i, -1] + min_len)
|
|
251
252
|
if offset < 0:
|
|
252
|
-
labels.iloc[ranges.iloc[i].end
|
|
253
|
+
labels.iloc[ranges.iloc[i].end+offset:ranges.iloc[i].end+1, labcols] = ranges.iloc[i+1].loc[kwargs.keys()]
|
|
253
254
|
return self.label(**labels.to_dict(orient='list'))
|
|
254
255
|
|
|
256
|
+
@classmethod
|
|
257
|
+
def shift_multi(Cls, steps: int, tb1: Self, tb2: Self, min_len=2) -> Tuple(Self, Self):
|
|
258
|
+
'''Take datapoints off the start of tb2 and add to the end tb1'''
|
|
259
|
+
if (steps>0 and len(tb2)-min_len<steps) or (steps<0 and len(tb1)-2<min_len):
|
|
260
|
+
raise ValueError(f'Cannot Squash a Table to less than {min_len} datapoints')
|
|
261
|
+
dfj = Cls.stack([tb1, tb2], overlap=0).data
|
|
262
|
+
return Cls(dfj.iloc[:len(tb1)+steps, :]), \
|
|
263
|
+
Cls(dfj.iloc[len(tb1)+steps:, :])
|
|
264
|
+
|
|
265
|
+
def shift_label_ratio(self, ratio: float, min_len=None, **kwargs) -> Self:
|
|
266
|
+
'''shift a label within its allowable bounds, with a ratio of
|
|
267
|
+
1 representing the maximum allowabe movement forwards or backwards
|
|
268
|
+
without squashing a label'''
|
|
269
|
+
ranges = self.label_ranges()
|
|
270
|
+
i = self.get_label_id(**kwargs)
|
|
271
|
+
if ratio > 0:
|
|
272
|
+
limit = ranges.iloc[i+1, -1] - 2
|
|
273
|
+
else:
|
|
274
|
+
limit = ranges.iloc[i, -1] -2
|
|
275
|
+
|
|
276
|
+
return self.shift_label(int(limit * ratio), min_len, **kwargs)
|
|
277
|
+
|
|
278
|
+
def shift_labels_ratios(self, ratios: list[float], min_len: int) -> Self:
|
|
279
|
+
assert len(ratios) == len(self.unique_labels())-1
|
|
280
|
+
res = self
|
|
281
|
+
for lab, ratio in zip([r[1] for r in self.unique_labels()[:-1].iterrows()], ratios):
|
|
282
|
+
res = res.shift_label_ratio(ratio, min_len, **lab)
|
|
283
|
+
return res
|
|
284
|
+
|
|
255
285
|
def get_label_id(self, **kwargs) -> Union[int, float]:
|
|
256
286
|
dfo = self.unique_labels()
|
|
257
287
|
for k, v in kwargs.items():
|
|
@@ -275,8 +305,10 @@ class Table:
|
|
|
275
305
|
res = []
|
|
276
306
|
for row in df.iterrows():
|
|
277
307
|
res.append(list(self.label_range(t=t,**row[1].to_dict())))
|
|
278
|
-
|
|
279
|
-
|
|
308
|
+
df = pd.concat([df, pd.DataFrame(res, columns=['start', 'end'])], axis=1)
|
|
309
|
+
df['length'] = df.end - df.start
|
|
310
|
+
return df
|
|
311
|
+
|
|
280
312
|
def single_labels(self) -> list[str]:
|
|
281
313
|
return ['_'.join(r[1]) for r in self.data.loc[:, self.label_cols].iterrows()]
|
|
282
314
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
from flightdata import Flight, Constructs, SVar, Table, Origin
|
|
3
|
-
from geometry import Point, Base, P0
|
|
2
|
+
from flightdata import Flight, Constructs, SVar, Table, Origin
|
|
3
|
+
from geometry import Point, Base, P0, Time
|
|
4
4
|
import numpy as np
|
|
5
5
|
from .wind import WindModel, WindModelBuilder
|
|
6
6
|
|
|
@@ -133,13 +133,12 @@ class Flight:
|
|
|
133
133
|
|
|
134
134
|
flos = []
|
|
135
135
|
for fl in fls:
|
|
136
|
-
flos.append(
|
|
137
|
-
pd.merge_asof(
|
|
136
|
+
flos.append(fl.copy(
|
|
137
|
+
data=pd.merge_asof(
|
|
138
138
|
otf,
|
|
139
139
|
fl.data.reset_index(),
|
|
140
140
|
on='time_actual'
|
|
141
|
-
).set_index('time_index', drop=False)
|
|
142
|
-
fl.parameters
|
|
141
|
+
).set_index('time_index', drop=False)
|
|
143
142
|
))
|
|
144
143
|
|
|
145
144
|
return flos
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
from flightdata import Table,
|
|
3
|
-
from geometry import Point, Base, PX, Euler
|
|
2
|
+
from flightdata import Table, SVar, Constructs, SVar, Flight, Origin
|
|
3
|
+
from geometry import Point, Base, PX, Euler, Time
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
6
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'''Take a flow and a controls, return the aerodynamic forces and moments'''
|
|
2
|
+
from flightdata import Coefficients
|
|
3
|
+
from . import Model
|
|
4
|
+
import numpy as np
|
|
5
|
+
import geometry as g
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SimpleAerodynamic(Model):
|
|
9
|
+
nparms=12
|
|
10
|
+
def __call__(self, time,alpha,beta,a,e,r):
|
|
11
|
+
c = self.parms
|
|
12
|
+
return Coefficients.from_constructs(
|
|
13
|
+
time,
|
|
14
|
+
force=g.Point(
|
|
15
|
+
c[0] + c[1] * alpha**2 ,
|
|
16
|
+
c[2] * beta,
|
|
17
|
+
c[3] + c[4] * alpha
|
|
18
|
+
),
|
|
19
|
+
moment=g.Point(
|
|
20
|
+
c[5] * a + c[6] * r,
|
|
21
|
+
c[7] + c[8] * alpha + c[9] * e,
|
|
22
|
+
c[10] * beta + c[11] * r
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
initial = [
|
|
27
|
+
0.03, 0.2,
|
|
28
|
+
2,
|
|
29
|
+
0.0, 3.0,
|
|
30
|
+
1.0, 1.0,
|
|
31
|
+
0.0, 1.0, 0.01,
|
|
32
|
+
0.2, 0.01
|
|
33
|
+
]
|
|
@@ -6,7 +6,7 @@ import numpy as np
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from pandas.api.types import is_list_like
|
|
8
8
|
import geometry as g
|
|
9
|
-
from flightdata import Table, Constructs, SVar,
|
|
9
|
+
from flightdata import Table, Constructs, SVar, Origin, Flow, Environment
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class State(Table):
|
|
@@ -33,7 +33,7 @@ class State(Table):
|
|
|
33
33
|
if transform is None:
|
|
34
34
|
transform = g.Transformation()
|
|
35
35
|
if not "time" in kwargs:
|
|
36
|
-
kwargs["time"] = Time.from_t(np.linspace(0, State._construct_freq*len(transform), len(transform)))
|
|
36
|
+
kwargs["time"] = g.Time.from_t(np.linspace(0, State._construct_freq*len(transform), len(transform)))
|
|
37
37
|
return State.from_constructs(pos=transform.p, att=transform.q, **kwargs)
|
|
38
38
|
|
|
39
39
|
def body_to_world(self, pin: g.Point, rotation_only=False) -> g.Point:
|
|
@@ -56,7 +56,7 @@ class State(Table):
|
|
|
56
56
|
else:
|
|
57
57
|
return self.back_transform.g.Point(pin)
|
|
58
58
|
|
|
59
|
-
def fill(self, time: Time) -> State:
|
|
59
|
+
def fill(self, time: g.Time) -> State:
|
|
60
60
|
'''Project forward through time assuming small angles and uniform circular motion'''
|
|
61
61
|
st = self[-1]
|
|
62
62
|
vel = st.vel.tile(len(time))
|
|
@@ -73,7 +73,7 @@ class State(Table):
|
|
|
73
73
|
"""Extrapolate the input state assuming uniform circular motion and small angles
|
|
74
74
|
"""
|
|
75
75
|
npoints = np.max([int(np.ceil(duration / self.dt[0])), min_len])
|
|
76
|
-
time = Time.from_t(np.linspace(0,duration, npoints))
|
|
76
|
+
time = g.Time.from_t(np.linspace(0,duration, npoints))
|
|
77
77
|
return self.fill(time)
|
|
78
78
|
|
|
79
79
|
@staticmethod
|
|
@@ -101,7 +101,7 @@ class State(Table):
|
|
|
101
101
|
elif origin is None:
|
|
102
102
|
origin = flight.origin
|
|
103
103
|
|
|
104
|
-
time = Time.from_t(np.array(flight.data.time_flight))
|
|
104
|
+
time = g.Time.from_t(np.array(flight.data.time_flight))
|
|
105
105
|
|
|
106
106
|
if all(flight.contains('gps')) and flight.primary_pos_source == 'gps':
|
|
107
107
|
pos = origin.rotation.transform_point(g.GPS(flight.gps) - origin.pos[0])
|
|
@@ -114,32 +114,9 @@ class State(Table):
|
|
|
114
114
|
vel = att.inverse().transform_point(origin.rotation.transform_point(g.Point(flight.velocity))) if all(flight.contains('velocity')) else None
|
|
115
115
|
rvel = g.Point(flight.axisrate) if all(flight.contains('axisrate')) else None
|
|
116
116
|
acc = g.Point(flight.acceleration) if all(flight.contains('acceleration')) else None
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
return State.from_constructs(time, pos, att, vel, rvel, acc)
|
|
119
119
|
|
|
120
|
-
@staticmethod
|
|
121
|
-
def stack(sections: list) -> Self:
|
|
122
|
-
"""Stack a list of States on top of each other. last row of each is replaced with first row of the next,
|
|
123
|
-
indexes are offset so they are sequential.
|
|
124
|
-
TODO this should move into the parent class.
|
|
125
|
-
"""
|
|
126
|
-
# first build list of index offsets, to be added to each dataframe
|
|
127
|
-
offsets = np.cumsum([0] + [sec.duration for sec in sections[:-1]])
|
|
128
|
-
|
|
129
|
-
# The sections to be stacked need their last row removed, as the first row of the next replaces it
|
|
130
|
-
dfs = [section.data.iloc[:-1] for section in sections[:-1]] + \
|
|
131
|
-
[sections[-1].data.copy()]
|
|
132
|
-
|
|
133
|
-
# offset the df indexes
|
|
134
|
-
for df, offset in zip(dfs, offsets):
|
|
135
|
-
df.index = np.array(df.index) - df.index[0] + offset
|
|
136
|
-
combo = pd.concat(dfs)
|
|
137
|
-
combo.index.name = "t"
|
|
138
|
-
|
|
139
|
-
combo["t"] = combo.index
|
|
140
|
-
|
|
141
|
-
return State(combo)
|
|
142
|
-
|
|
143
120
|
@staticmethod
|
|
144
121
|
def align(
|
|
145
122
|
flown: State,
|
|
@@ -169,8 +146,10 @@ class State(Table):
|
|
|
169
146
|
dist=euclidean
|
|
170
147
|
)
|
|
171
148
|
|
|
172
|
-
return distance, State.copy_labels(template, flown, path,
|
|
149
|
+
return distance, State.copy_labels(template, flown, path, 3)
|
|
173
150
|
|
|
151
|
+
|
|
152
|
+
|
|
174
153
|
def splitter_labels(self: State, mans: List[dict], better_names: List[str]=None) -> State:
|
|
175
154
|
"""label the manoeuvres in a State based on the flight coach splitter information
|
|
176
155
|
|
|
@@ -209,6 +188,7 @@ class State(Table):
|
|
|
209
188
|
|
|
210
189
|
return State.stack(labelled)
|
|
211
190
|
|
|
191
|
+
|
|
212
192
|
def get_subset(self: State, mans: Union[list, slice], col="manoeuvre", min_len=1) -> Self:
|
|
213
193
|
selectors = self.data.loc[:,col].unique()
|
|
214
194
|
if isinstance(mans, slice):
|
|
@@ -231,7 +211,7 @@ class State(Table):
|
|
|
231
211
|
|
|
232
212
|
def get_element(self: State, element: Union[str, list, int]) -> Self:
|
|
233
213
|
return self.get_subset(element, "element")
|
|
234
|
-
|
|
214
|
+
|
|
235
215
|
def get_man_or_el(self: State, el: str) -> Self:
|
|
236
216
|
if el in self.data.element.unique():
|
|
237
217
|
return self.get_element([el])
|
|
@@ -270,6 +250,15 @@ class State(Table):
|
|
|
270
250
|
racc=self.racc,
|
|
271
251
|
))
|
|
272
252
|
|
|
253
|
+
def mirror_zy(self: State) -> State:
|
|
254
|
+
att = g.Quaternion.from_euler((self.att.to_euler() + g.Point(0, 0, np.pi)) * g.Point(-1, 1, -1))
|
|
255
|
+
return State.copy_labels(self, State.from_constructs(
|
|
256
|
+
time=self.time,
|
|
257
|
+
pos=self.pos * g.Point(-1, 1, 1),
|
|
258
|
+
att=att,#g.Quaternion(self.att.w, self.att.x, -self.att.y, -self.att.z),
|
|
259
|
+
vel=self.vel
|
|
260
|
+
))
|
|
261
|
+
|
|
273
262
|
def to_track(self: State) -> State:
|
|
274
263
|
"""This rotates the body so the x axis is in the velocity vector"""
|
|
275
264
|
return self.body_to_wind()
|
|
@@ -360,34 +349,35 @@ class State(Table):
|
|
|
360
349
|
|
|
361
350
|
return fcd
|
|
362
351
|
|
|
363
|
-
def _create_json_mans(self: State,
|
|
352
|
+
def _create_json_mans(self: State, kfactors: list[int]) -> pd.DataFrame:
|
|
353
|
+
|
|
364
354
|
mans = pd.DataFrame(columns=["name", "id", "sp", "wd", "start", "stop", "sel", "background", "k"])
|
|
355
|
+
mnames = self.data.manoeuvre.unique()
|
|
356
|
+
mans["name"] = mnames
|
|
357
|
+
mans["k"] = kfactors
|
|
358
|
+
mans["id"] = ["sp_{}".format(i) for i in range(len(mnames))]
|
|
365
359
|
|
|
366
|
-
mans["
|
|
367
|
-
mans["k"] = [0] + [man.info.k for man in sdef]
|
|
368
|
-
mans["id"] = ["sp_{}".format(i) for i in range(len(sdef.data)+1)]
|
|
369
|
-
|
|
370
|
-
mans["sp"] = list(range(len(sdef.data) + 1))
|
|
360
|
+
mans["sp"] = list(range(len(mnames)))
|
|
371
361
|
|
|
372
|
-
itsecs = [self.get_manoeuvre(
|
|
362
|
+
itsecs = [self.get_manoeuvre(mn) for mn in mnames]
|
|
373
363
|
|
|
374
|
-
mans["wd"] = [
|
|
364
|
+
mans["wd"] = [100 * st.duration / self.duration for st in itsecs]
|
|
375
365
|
|
|
376
366
|
dat = self.data.reset_index(drop=True)
|
|
377
367
|
|
|
378
|
-
mans["start"] = [
|
|
368
|
+
mans["start"] = [dat.loc[dat.manoeuvre==mn].index[0] for mn in mnames]
|
|
379
369
|
|
|
380
|
-
mans["stop"] = [
|
|
370
|
+
mans["stop"] = [dat.loc[dat.manoeuvre==mn].index[-1] + 1 for mn in mnames]
|
|
381
371
|
|
|
382
|
-
mans["sel"] = np.full(len(
|
|
372
|
+
mans["sel"] = np.full(len(mnames.data), False)
|
|
383
373
|
mans.loc[1,"sel"] = True
|
|
384
|
-
mans["background"] = np.full(len(
|
|
374
|
+
mans["background"] = np.full(len(mnames), "")
|
|
385
375
|
|
|
386
376
|
return mans
|
|
387
377
|
|
|
388
|
-
def create_fc_json(self: State,
|
|
378
|
+
def create_fc_json(self: State, kfactors: list[int], schedule_name: str, schedule_category: str="F3A"):
|
|
389
379
|
fcdata = self._create_json_data()
|
|
390
|
-
fcmans = self._create_json_mans(
|
|
380
|
+
fcmans = self._create_json_mans(kfactors)
|
|
391
381
|
return {
|
|
392
382
|
"version": "1.3",
|
|
393
383
|
"comments": "DO NOT EDIT\n",
|
|
@@ -558,5 +548,20 @@ class State(Table):
|
|
|
558
548
|
|
|
559
549
|
def arc_centre(self) -> g.Point:
|
|
560
550
|
acc = g.Point.vector_rejection(self.zero_g_acc(), self.vel)
|
|
561
|
-
|
|
562
|
-
|
|
551
|
+
with np.errstate(invalid='ignore'):
|
|
552
|
+
return acc.unit() * abs(self.vel) ** 2 / abs(acc)
|
|
553
|
+
|
|
554
|
+
def F_gravity(self, mass: g.Mass):
|
|
555
|
+
'''Returns the gravitational force in N'''
|
|
556
|
+
return mass.m * self.att.inverse().transform_point(g.PZ(-9.81))
|
|
557
|
+
|
|
558
|
+
def F_inertia(self, mass: g.Mass):
|
|
559
|
+
'''Returns the inertial force in N'''
|
|
560
|
+
return mass.m * (self.zero_g_acc() + g.Point.cross(self.rvel, self.vel))
|
|
561
|
+
|
|
562
|
+
def M_inertia(self, mass: g.Mass):
|
|
563
|
+
'''return the inertial moment in N'''
|
|
564
|
+
h = mass.angular_momentum(self.rvel)
|
|
565
|
+
return h.diff(self.dt) + g.Point.cross(self.rvel, h)
|
|
566
|
+
|
|
567
|
+
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flightdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Module for handling UAV flight log data
|
|
5
5
|
Home-page: https://github.com/PyFlightCoach/FlightData
|
|
6
6
|
Author: Thomas David
|
|
7
7
|
Author-email: thomasdavid0@gmail.com
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: pfc-geometry
|
|
10
13
|
|
|
11
14
|
## FlightData
|
|
12
15
|
This repo is contains a set of datastructures and tools for handling flight log data.
|
|
@@ -3,6 +3,8 @@ README.md
|
|
|
3
3
|
setup.cfg
|
|
4
4
|
setup.py
|
|
5
5
|
flightdata/__init__.py
|
|
6
|
+
flightdata/coefficients.py
|
|
7
|
+
flightdata/flow.py
|
|
6
8
|
flightdata/origin.py
|
|
7
9
|
flightdata/state.py
|
|
8
10
|
flightdata.egg-info/PKG-INFO
|
|
@@ -23,9 +25,8 @@ flightdata/flight/ardupilot.py
|
|
|
23
25
|
flightdata/flight/fields.py
|
|
24
26
|
flightdata/flight/flight.py
|
|
25
27
|
flightdata/model/__init__.py
|
|
26
|
-
flightdata/model/
|
|
27
|
-
flightdata/model/
|
|
28
|
-
flightdata/model/flow.py
|
|
28
|
+
flightdata/model/aerodynamic.py
|
|
29
|
+
flightdata/model/thrust.py
|
|
29
30
|
test/test_fields.py
|
|
30
31
|
test/test_flight.py
|
|
31
32
|
test/test_origin.py
|
|
@@ -5,7 +5,7 @@ author_email = thomasdavid0@gmail.com
|
|
|
5
5
|
description = Module for handling UAV flight log data
|
|
6
6
|
long_description = file: README.md
|
|
7
7
|
long_description_content_type = text/markdown
|
|
8
|
-
version = 0.2.
|
|
8
|
+
version = 0.2.3
|
|
9
9
|
url = https://github.com/PyFlightCoach/FlightData
|
|
10
10
|
|
|
11
11
|
[options]
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from geometry import Point, Mass
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
|
|
4
|
-
@dataclass
|
|
5
|
-
class ACConstants:
|
|
6
|
-
s: float
|
|
7
|
-
c: float
|
|
8
|
-
b: float
|
|
9
|
-
mass: Mass
|
|
10
|
-
cg: Point
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
cold_draft = ACConstants(
|
|
14
|
-
0.569124,
|
|
15
|
-
0.31211,
|
|
16
|
-
1.8594,
|
|
17
|
-
Mass.cuboid(4.5, 800, 400, 100),
|
|
18
|
-
Point(0.6192,0.0,0.0)
|
|
19
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|