flightplotting 0.2.13__tar.gz → 0.2.14__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.
- {flightplotting-0.2.13 → flightplotting-0.2.14}/PKG-INFO +2 -4
- {flightplotting-0.2.13 → flightplotting-0.2.14}/pyproject.toml +2 -4
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/__init__.py +1 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/plots.py +167 -11
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/traces.py +2 -2
- {flightplotting-0.2.13 → flightplotting-0.2.14}/.github/workflows/publish_pypi.yml +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/.gitignore +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/.vscode/settings.json +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/COPYING +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/MANIFEST.in +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/README.md +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/data/ColdDraftF3APlane.obj +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/data/__init__.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/model.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/py.typed +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/templates.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/src/plotting/titlerenderer.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/tests/__init__.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/tests/data/__init__.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/tests/data/p23_flight.json +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/tests/test_plots.py +0 -0
- {flightplotting-0.2.13 → flightplotting-0.2.14}/uv.lock +0 -0
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flightplotting
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author-email: Thomas David <thomasdavid0@gmail.com>
|
|
6
6
|
License-File: COPYING
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
|
-
Requires-Dist: flightdata>=0.3.
|
|
8
|
+
Requires-Dist: flightdata>=0.3.5
|
|
9
9
|
Requires-Dist: numpy>=2.1.3
|
|
10
10
|
Requires-Dist: pandas>=2.2.3
|
|
11
|
-
Requires-Dist: pfc-geometry
|
|
12
|
-
Requires-Dist: pfcschemas
|
|
13
11
|
Requires-Dist: plotly>=5.24.1
|
|
14
12
|
Description-Content-Type: text/markdown
|
|
15
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flightplotting"
|
|
3
|
-
version="v0.2.
|
|
3
|
+
version="v0.2.14"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Thomas David", email = "thomasdavid0@gmail.com" }]
|
|
@@ -9,9 +9,7 @@ dependencies = [
|
|
|
9
9
|
"numpy>=2.1.3",
|
|
10
10
|
"pandas>=2.2.3",
|
|
11
11
|
"plotly>=5.24.1",
|
|
12
|
-
"flightdata>=0.3.
|
|
13
|
-
"pfc-geometry",
|
|
14
|
-
"pfcschemas",
|
|
12
|
+
"flightdata>=0.3.5",
|
|
15
13
|
]
|
|
16
14
|
|
|
17
15
|
[build-system]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import plotly.graph_objects as go
|
|
2
3
|
from plotly.subplots import make_subplots
|
|
3
4
|
import plotly.express as px
|
|
@@ -15,14 +16,18 @@ from plotting.traces import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from flightdata import State
|
|
18
|
-
|
|
19
|
+
import geometry as g
|
|
19
20
|
from plotting.model import obj
|
|
20
21
|
import numpy.typing as npt
|
|
21
22
|
import numpy as np
|
|
22
23
|
import pandas as pd
|
|
23
|
-
from typing import List, Union
|
|
24
|
+
from typing import List, Union, Callable, Literal
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
|
|
28
|
+
def get_colour(i):
|
|
29
|
+
return px.colors.qualitative.Plotly[i % len(px.colors.qualitative.Plotly)]
|
|
30
|
+
|
|
26
31
|
def plotsec(
|
|
27
32
|
secs: State | list[State] | dict[str, State],
|
|
28
33
|
scale=5,
|
|
@@ -37,6 +42,8 @@ def plotsec(
|
|
|
37
42
|
tips: bool = True,
|
|
38
43
|
ribbonhover="t",
|
|
39
44
|
origin=False,
|
|
45
|
+
line=None,
|
|
46
|
+
modelscale=1,
|
|
40
47
|
):
|
|
41
48
|
traces = []
|
|
42
49
|
keys = None
|
|
@@ -50,24 +57,32 @@ def plotsec(
|
|
|
50
57
|
else:
|
|
51
58
|
keys = list(range(len(secs)))
|
|
52
59
|
showkeys = False
|
|
60
|
+
|
|
61
|
+
def _get_colour(i):
|
|
62
|
+
if isinstance(color, list):
|
|
63
|
+
return color[i % len(color)]
|
|
64
|
+
elif isinstance(color, str):
|
|
65
|
+
return color
|
|
66
|
+
else:
|
|
67
|
+
return get_colour(i)
|
|
53
68
|
|
|
54
69
|
for i, sec in enumerate(secs):
|
|
55
70
|
text = sec.data.t # - sec.data.t.iloc[0]
|
|
56
|
-
|
|
71
|
+
|
|
57
72
|
if ribb:
|
|
58
|
-
traces += ribbon(sec, 0.5 * scale * 1.85,
|
|
73
|
+
traces += ribbon(sec, 0.5 * scale * 1.85, _get_colour(i), name=keys[i], opacity=0.5, hover=ribbonhover)
|
|
59
74
|
if tips:
|
|
60
|
-
traces += tiptrace(sec, scale * 1.85, text=text, name=keys[i])
|
|
75
|
+
traces += tiptrace(sec, scale * 1.85, text=text, name=keys[i], line=({} if line is None else line))
|
|
61
76
|
if nmodels > 0:
|
|
62
|
-
traces += meshes(nmodels, sec,
|
|
77
|
+
traces += meshes(nmodels, sec, _get_colour(i), scale * modelscale)
|
|
63
78
|
if cg:
|
|
64
79
|
traces.append(
|
|
65
|
-
cgtrace(sec, line=dict(color=
|
|
80
|
+
cgtrace(sec, line=dict(color=_get_colour(i), width=2) | ({} if line is None else line), name=keys[i], text=text)
|
|
66
81
|
)
|
|
67
82
|
|
|
68
83
|
if origin:
|
|
69
|
-
traces += axestrace(Coord.zero(), 50)
|
|
70
|
-
|
|
84
|
+
traces += axestrace(g.Coord.zero(), 50)
|
|
85
|
+
|
|
71
86
|
if showkeys:
|
|
72
87
|
for i, key in enumerate(keys):
|
|
73
88
|
traces.append(
|
|
@@ -76,7 +91,7 @@ def plotsec(
|
|
|
76
91
|
y=[],
|
|
77
92
|
z=[],
|
|
78
93
|
mode="markers",
|
|
79
|
-
marker=dict(size=5, color=
|
|
94
|
+
marker=dict(size=5, color=_get_colour(i)),
|
|
80
95
|
name=key,
|
|
81
96
|
showlegend=True,
|
|
82
97
|
)
|
|
@@ -85,7 +100,7 @@ def plotsec(
|
|
|
85
100
|
if fig is None:
|
|
86
101
|
fig = go.Figure(
|
|
87
102
|
data=traces,
|
|
88
|
-
layout=go.Layout(template="flight3d
|
|
103
|
+
layout=go.Layout(template="flight3d", uirevision="foo"),
|
|
89
104
|
)
|
|
90
105
|
if show_axes:
|
|
91
106
|
fig.update_layout(
|
|
@@ -359,3 +374,144 @@ axis = dict(
|
|
|
359
374
|
zerolinecolor="lightgrey",
|
|
360
375
|
showline=True,
|
|
361
376
|
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def create_ortho_state(st: State, axis: Literal['x', 'z'], width: g.Point, gap: g.Point) -> State:
|
|
380
|
+
"""rotate by 90 degrees about the given axis,
|
|
381
|
+
then move to where it should be in an orthographic projection.
|
|
382
|
+
assumes front view is along the Y axis (x right, z up).
|
|
383
|
+
also assumes state is centered at the origin.
|
|
384
|
+
"""
|
|
385
|
+
st = st.move(g.Transformation(g.Euler(
|
|
386
|
+
np.pi/2 if axis=="x" else 0,
|
|
387
|
+
0,
|
|
388
|
+
np.pi/2 if axis=="z" else 0
|
|
389
|
+
)))
|
|
390
|
+
|
|
391
|
+
if axis == 'x':
|
|
392
|
+
shift = g.PZ((width.y + width.z) / 2 + gap)
|
|
393
|
+
elif axis == 'z':
|
|
394
|
+
shift = g.PX(-(width.x + width.y) / 2 - gap)
|
|
395
|
+
|
|
396
|
+
st = st.move(g.Transformation( shift) ) # move back to center and offset by shift
|
|
397
|
+
|
|
398
|
+
return st
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def applysts(st: State | list[State] | dict[str, State], fun: Callable[[State], State]) -> Union[State, List[State], dict[str, State]]:
|
|
402
|
+
"""apply a transformation to all states in a list or dict of states"""
|
|
403
|
+
if isinstance(st, State):
|
|
404
|
+
return fun(st)
|
|
405
|
+
elif isinstance(st, list):
|
|
406
|
+
return [fun(s) for s in st]
|
|
407
|
+
elif isinstance(st, dict):
|
|
408
|
+
return {k: fun(v) for k, v in st.items()}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def get_points(fig: go.Figure) -> g.Point:
|
|
412
|
+
"""extract all points from a figure"""
|
|
413
|
+
ps = []
|
|
414
|
+
for d in fig.data:
|
|
415
|
+
try:
|
|
416
|
+
ps.append(g.Point(d.x, d.y, d.z))
|
|
417
|
+
except Exception:
|
|
418
|
+
pass
|
|
419
|
+
return g.Point.concatenate(ps)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def plot_3view(st: State | list[State] | dict[str, State], plotfun: Callable, gap: float, legend_vstep=10):
|
|
423
|
+
|
|
424
|
+
allsts = State.stack(st, "grp") if not isinstance(st, State) else st
|
|
425
|
+
|
|
426
|
+
width = allsts.pos.max() - allsts.pos.min()
|
|
427
|
+
center = allsts.pos.min() + width / 2
|
|
428
|
+
|
|
429
|
+
st0 = applysts(st, lambda s: s.move(g.Transformation(-center)))
|
|
430
|
+
st1 = applysts(st0, lambda s: create_ortho_state(s, 'x', width, gap))
|
|
431
|
+
st2 = applysts(st0, lambda s: create_ortho_state(s, 'z', width, gap))
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
fig = go.Figure(data=
|
|
435
|
+
plotfun(st0) + plotfun(st1) + plotfun(st2)
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
anprops = dict(
|
|
439
|
+
showarrow=False,
|
|
440
|
+
font=dict(size=16, family="Rockwell"),
|
|
441
|
+
xanchor="center",
|
|
442
|
+
yanchor="middle",
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
fig = fig.update_layout(
|
|
447
|
+
template="plotly_white",
|
|
448
|
+
scene=dict(
|
|
449
|
+
camera=dict(
|
|
450
|
+
eye=dict(x=0, y=-1, z=0),
|
|
451
|
+
center=dict(x=0, y=0, z=0),
|
|
452
|
+
projection=dict(type="orthographic"),
|
|
453
|
+
),
|
|
454
|
+
xaxis=dict(visible=False),
|
|
455
|
+
yaxis=dict(visible=False),
|
|
456
|
+
zaxis=dict(visible=False),
|
|
457
|
+
annotations=[
|
|
458
|
+
dict(
|
|
459
|
+
x=0, y=0, z=-width.z[0] / 2 - gap/2,
|
|
460
|
+
text="Front View",
|
|
461
|
+
|
|
462
|
+
) | anprops,
|
|
463
|
+
dict(
|
|
464
|
+
x=0, y=0, z=width.z[0] / 2 + width.y[0] / 2 + gap/2,
|
|
465
|
+
text="Top View",
|
|
466
|
+
) | anprops
|
|
467
|
+
,
|
|
468
|
+
dict(
|
|
469
|
+
x=-width.x[0] / 2 - width.y[0] / 2 - gap, y=0, z=-width.z[0] / 2 - gap/2,
|
|
470
|
+
text="Left View",
|
|
471
|
+
) | anprops
|
|
472
|
+
] + ([] if not isinstance(st, dict) else [dict(
|
|
473
|
+
x=-width.x[0] / 2 - width.y[0] / 2 - gap,
|
|
474
|
+
y=0,
|
|
475
|
+
z=width.z[0] / 2 + width.y[0] / 2 + gap/2 + i * legend_vstep,
|
|
476
|
+
text=k,
|
|
477
|
+
font=dict(size=16, family="Rockwell", color=px.colors.qualitative.Plotly[i]),
|
|
478
|
+
showarrow=False,
|
|
479
|
+
) for i, k in enumerate(st.keys())] )
|
|
480
|
+
),
|
|
481
|
+
margin=dict(l=0, r=0, b=0, t=0),
|
|
482
|
+
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
return resize_3d_fig(fig, 600, False)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def resize_3d_fig(fig: go.Figure, width: int | None, width_is_height: bool=False, scale: float=1):
|
|
489
|
+
"""Resize a figure to the given width, height and zoom level.
|
|
490
|
+
preserves the aspect ratio of the scene.
|
|
491
|
+
Assumes view is in the positive Y direction
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
all_points = get_points(fig)
|
|
495
|
+
|
|
496
|
+
btm_left = all_points.min()
|
|
497
|
+
top_right = all_points.max()
|
|
498
|
+
|
|
499
|
+
bb = (top_right - btm_left)
|
|
500
|
+
width = width or (fig.layout.height if width_is_height else fig.layout.width) or 600
|
|
501
|
+
zoom = (0.008 * scale*width/(bb.z[0] if width_is_height else bb.x[0]))
|
|
502
|
+
ar = bb * zoom
|
|
503
|
+
height = ar.x[0] * width / ar.z[0] if width_is_height else ar.z[0] * width / ar.x[0]
|
|
504
|
+
fig.update_layout(
|
|
505
|
+
width=height if width_is_height else width,
|
|
506
|
+
height=width if width_is_height else height,
|
|
507
|
+
scene=dict(
|
|
508
|
+
aspectratio=dict(x=ar.x[0], y=ar.y[0], z=ar.z[0]),
|
|
509
|
+
camera=dict(
|
|
510
|
+
eye=dict(x=0, y=-1, z=0),
|
|
511
|
+
center=dict(x=0, y=0, z=0),
|
|
512
|
+
projection=dict(type="orthographic"),
|
|
513
|
+
)
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
return fig
|
|
@@ -107,12 +107,12 @@ def elementtraces(manoeuvre, sec: State):
|
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
def tiptrace(seq, span, **kwargs):
|
|
110
|
+
def tiptrace(seq, span, line=None, **kwargs):
|
|
111
111
|
|
|
112
112
|
def make_offset_trace(pos, colour):
|
|
113
113
|
tr = trace3d(
|
|
114
114
|
*seq.body_to_world(pos).data.T,
|
|
115
|
-
**dict(
|
|
115
|
+
**dict(line=dict(color=colour, width=1) | ({} if line is None else line), **kwargs)
|
|
116
116
|
)
|
|
117
117
|
tr['showlegend'] = False
|
|
118
118
|
return tr
|
|
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
|