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.
Files changed (36) hide show
  1. {flightdata-0.2.1 → flightdata-0.2.3}/PKG-INFO +4 -1
  2. flightdata-0.2.3/flightdata/__init__.py +8 -0
  3. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/__init__.py +1 -1
  4. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/collection.py +4 -4
  5. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/table.py +74 -42
  6. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/environment/environment.py +2 -2
  7. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/flight.py +3 -4
  8. {flightdata-0.2.1/flightdata/model → flightdata-0.2.3/flightdata}/flow.py +2 -2
  9. flightdata-0.2.3/flightdata/model/__init__.py +11 -0
  10. flightdata-0.2.3/flightdata/model/aerodynamic.py +33 -0
  11. flightdata-0.2.3/flightdata/model/thrust.py +10 -0
  12. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/origin.py +1 -0
  13. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/state.py +52 -47
  14. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/PKG-INFO +4 -1
  15. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/SOURCES.txt +4 -3
  16. {flightdata-0.2.1 → flightdata-0.2.3}/setup.cfg +1 -1
  17. flightdata-0.2.1/flightdata/__init__.py +0 -6
  18. flightdata-0.2.1/flightdata/model/__init__.py +0 -3
  19. flightdata-0.2.1/flightdata/model/constants.py +0 -19
  20. {flightdata-0.2.1 → flightdata-0.2.3}/LICENSE +0 -0
  21. {flightdata-0.2.1 → flightdata-0.2.3}/README.md +0 -0
  22. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/constructs.py +0 -0
  23. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/base/numpy_encoder.py +0 -0
  24. {flightdata-0.2.1/flightdata/model → flightdata-0.2.3/flightdata}/coefficients.py +0 -0
  25. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/environment/__init__.py +0 -0
  26. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/environment/wind.py +0 -0
  27. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/__init__.py +0 -0
  28. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/ardupilot.py +0 -0
  29. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata/flight/fields.py +0 -0
  30. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/dependency_links.txt +0 -0
  31. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/requires.txt +0 -0
  32. {flightdata-0.2.1 → flightdata-0.2.3}/flightdata.egg-info/top_level.txt +0 -0
  33. {flightdata-0.2.1 → flightdata-0.2.3}/setup.py +0 -0
  34. {flightdata-0.2.1 → flightdata-0.2.3}/test/test_fields.py +0 -0
  35. {flightdata-0.2.1 → flightdata-0.2.3}/test/test_flight.py +0 -0
  36. {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.1
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.
@@ -0,0 +1,8 @@
1
+ from .base import *
2
+ from .origin import Origin
3
+ from .flight import *
4
+ from .environment import *
5
+ from .coefficients import Coefficients
6
+ from .flow import Flow, Attack
7
+ from .state import State
8
+ from .model import *
@@ -1,4 +1,4 @@
1
- from .table import Time, Table, Constructs, SVar
1
+ from .table import Table, Constructs, SVar
2
2
  from .collection import Collection
3
3
  from .numpy_encoder import NumpyEncoder
4
4
 
@@ -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, Point, Quaternion, Transformation
6
- from typing import Union, Dict, Self
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
- SVar("time", Time, ["t", "dt"] , make_time )
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, self.get_label_len(**ranges.iloc[i+1, :2].to_dict()) - min_len)
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, :] = pd.Series(kwargs)
249
- elif offset < 0 and i > 0:
250
- offset = max(offset, -self.get_label_len(**kwargs) + min_len)
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-offset:ranges.iloc[i].end, :] = pd.Series(kwargs)
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
- return pd.concat([df, pd.DataFrame(res, columns=['start', 'end'])], axis=1)
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, Time
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(Flight(
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, Time, SVar, Constructs, SVar, Flight, Origin
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,11 @@
1
+ class Model:
2
+ def __init__(self, *parms):
3
+ self.parms = parms
4
+
5
+ def __call__(self, *args):
6
+ raise NotImplementedError("Model is an abstract class")
7
+
8
+
9
+ from .aerodynamic import SimpleAerodynamic
10
+ from .thrust import SimplePropeller
11
+
@@ -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
+ ]
@@ -0,0 +1,10 @@
1
+ from . import Model
2
+
3
+ class SimplePropeller(Model):
4
+ nparms = 2
5
+ def __call__(self, u, pin):
6
+ c = self.parms
7
+ return c[0] + c[1] * pin / u
8
+
9
+
10
+ initial = [0.0, -1.0]
@@ -137,3 +137,4 @@ class Origin(object):
137
137
  return self.rotation.transform_point(g.Point(pned.y, pned.x, -pned.z ))
138
138
 
139
139
 
140
+
@@ -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, Time, Origin, Flow, Environment
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, 2)
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, sdef) -> pd.DataFrame:
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["name"] = ["tkoff"] + [man.info.short_name for man in sdef]
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(m.info.short_name) for m in sdef]
362
+ itsecs = [self.get_manoeuvre(mn) for mn in mnames]
373
363
 
374
- mans["wd"] = [0.0] + [100 * st.duration / self.duration for st in itsecs]
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"] = [0] + [dat.loc[dat.manoeuvre==man.info.short_name].index[0] for man in sdef]
368
+ mans["start"] = [dat.loc[dat.manoeuvre==mn].index[0] for mn in mnames]
379
369
 
380
- mans["stop"] = [mans["start"][1] + 1] + [dat.loc[dat.manoeuvre==man.info.short_name].index[-1] + 1 for man in sdef]
370
+ mans["stop"] = [dat.loc[dat.manoeuvre==mn].index[-1] + 1 for mn in mnames]
381
371
 
382
- mans["sel"] = np.full(len(sdef.data) + 1, False)
372
+ mans["sel"] = np.full(len(mnames.data), False)
383
373
  mans.loc[1,"sel"] = True
384
- mans["background"] = np.full(len(sdef.data) + 1, "")
374
+ mans["background"] = np.full(len(mnames), "")
385
375
 
386
376
  return mans
387
377
 
388
- def create_fc_json(self: State, sdef, schedule_name: str, schedule_category: str="F3A"):
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(sdef)
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
- return acc.unit() * abs(self.vel) ** 2 / abs(acc)
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.1
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/coefficients.py
27
- flightdata/model/constants.py
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.1
8
+ version = 0.2.3
9
9
  url = https://github.com/PyFlightCoach/FlightData
10
10
 
11
11
  [options]
@@ -1,6 +0,0 @@
1
- from .base import *
2
- from .origin import *
3
- from .flight import *
4
- from .environment import *
5
- from .model import *
6
- from .state import *
@@ -1,3 +0,0 @@
1
- from .flow import Flow
2
- from .coefficients import Coefficients
3
- from .constants import ACConstants, cold_draft
@@ -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