Skip to content

TrajectoryData

HitResult dataclass

HitResult(shot: Shot, trajectory: list[TrajectoryData], extra: bool = False)

Results of the shot

zeros

zeros() -> list[TrajectoryData]

:return: zero crossing points

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
193
194
195
196
197
198
199
def zeros(self) -> list[TrajectoryData]:
    """:return: zero crossing points"""
    self.__check_extra__()
    data = [row for row in self.trajectory if row.flag & TrajFlag.ZERO]
    if len(data) < 1:
        raise ArithmeticError("Can't find zero crossing points")
    return data

index_at_distance

index_at_distance(d: Distance) -> int

:param d: Distance for which we want Trajectory Data :return: Index of first trajectory row with .distance >= d; otherwise -1

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
201
202
203
204
205
206
207
208
def index_at_distance(self, d: Distance) -> int:
    """
    :param d: Distance for which we want Trajectory Data
    :return: Index of first trajectory row with .distance >= d; otherwise -1
    """
    # Get index of first trajectory point with distance >= at_range
    return next((i for i in range(len(self.trajectory))
                 if self.trajectory[i].distance >= d), -1)

get_at_distance

get_at_distance(d: Distance) -> TrajectoryData

:param d: Distance for which we want Trajectory Data :return: First trajectory row with .distance >= d

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
210
211
212
213
214
215
216
217
218
219
def get_at_distance(self, d: Distance) -> TrajectoryData:
    """
    :param d: Distance for which we want Trajectory Data
    :return: First trajectory row with .distance >= d
    """
    if (i := self.index_at_distance(d)) < 0:
        raise ArithmeticError(
            f"Calculated trajectory doesn't reach requested distance {d}"
        )
    return self.trajectory[i]

danger_space

danger_space(at_range: Union[float, Distance], target_height: Union[float, Distance], look_angle: Optional[Union[float, Angular]] = None) -> DangerSpace

Assume that the trajectory hits the center of a target at any distance. Now we want to know how much ranging error we can tolerate if the critical region of the target has height h. I.e., we want to know how far forward and backward along the line of sight we can move a target such that the trajectory is still within h/2 of the original drop.

:param at_range: Danger space is calculated for a target centered at this sight distance :param target_height: Target height (h) determines danger space :param look_angle: Ranging errors occur along the look angle to the target

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def danger_space(self,
                 at_range: Union[float, Distance],
                 target_height: Union[float, Distance],
                 look_angle: Optional[Union[float, Angular]] = None
                 ) -> DangerSpace:
    """
    Assume that the trajectory hits the center of a target at any distance.
    Now we want to know how much ranging error we can tolerate if the critical region
    of the target has height *h*.  I.e., we want to know how far forward and backward
    along the line of sight we can move a target such that the trajectory is still
    within *h*/2 of the original drop.

    :param at_range: Danger space is calculated for a target centered at this sight distance
    :param target_height: Target height (*h*) determines danger space
    :param look_angle: Ranging errors occur along the look angle to the target
    """
    self.__check_extra__()

    at_range = PreferredUnits.distance(at_range)
    target_height = PreferredUnits.distance(target_height)
    target_height_half = target_height.raw_value / 2.0

    _look_angle: Angular
    if look_angle is None:
        _look_angle = self.shot.look_angle
    else:
        _look_angle = PreferredUnits.angular(look_angle)

    # Get index of first trajectory point with distance >= at_range
    if (index := self.index_at_distance(at_range)) < 0:
        raise ArithmeticError(
            f"Calculated trajectory doesn't reach requested distance {at_range}"
        )

    def find_begin_danger(row_num: int) -> TrajectoryData:
        """
        Beginning of danger space is last .distance' < .distance where
            (.drop' - target_center) >= target_height/2
        :param row_num: Index of the trajectory point for which we are calculating danger space
        :return: Distance marking beginning of danger space
        """
        center_row = self.trajectory[row_num]
        for prime_row in reversed(self.trajectory[:row_num]):
            if (prime_row.target_drop.raw_value - center_row.target_drop.raw_value) >= target_height_half:
                return prime_row
        return self.trajectory[0]

    def find_end_danger(row_num: int) -> TrajectoryData:
        """
        End of danger space is first .distance' > .distance where
            (target_center - .drop') >= target_height/2
        :param row_num: Index of the trajectory point for which we are calculating danger space
        :return: Distance marking end of danger space
        """
        center_row = self.trajectory[row_num]
        for prime_row in self.trajectory[row_num + 1:]:
            if (center_row.target_drop.raw_value - prime_row.target_drop.raw_value) >= target_height_half:
                return prime_row
        return self.trajectory[-1]

    return DangerSpace(self.trajectory[index],
                       target_height,
                       find_begin_danger(index),
                       find_end_danger(index),
                       _look_angle)

dataframe

dataframe(formatted: bool = False) -> DataFrame

:param formatted: False for values as floats; True for strings with prefer_units :return: the trajectory table as a DataFrame

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
289
290
291
292
293
294
295
296
297
298
299
300
def dataframe(self, formatted: bool = False) -> 'DataFrame':  # type: ignore
    """
    :param formatted: False for values as floats; True for strings with prefer_units
    :return: the trajectory table as a DataFrame
    """
    try:
        from py_ballisticcalc.visualize.dataframe import hit_result_as_dataframe
        return hit_result_as_dataframe(self, formatted)
    except ImportError as err:
        raise ImportError(
            "Use `pip install py_ballisticcalc[charts]` to get trajectory as pandas.DataFrame"
        )from err

plot

plot(look_angle: Optional[Angular] = None) -> Axes

:return: graph of the trajectory

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
303
304
305
306
307
308
309
310
311
def plot(self, look_angle: Optional[Angular] = None) -> 'Axes':  # type: ignore
    """:return: graph of the trajectory"""
    try:
        from py_ballisticcalc.visualize.plot import hit_result_as_plot  # type: ignore
        return hit_result_as_plot(self, look_angle)
    except ImportError as err:
        raise ImportError(
            "Use `pip install py_ballisticcalc[charts]` to get results as a plot"
        ) from err

TrajFlag

Bases: int

Flags for marking trajectory row if Zero or Mach crossing Also uses to set a filters for a trajectory calculation loop

name staticmethod

name(value: Union[int, TrajFlag]) -> str

Return a concatenated name representation of the given flag value.

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
39
40
41
42
43
44
45
46
47
48
49
@staticmethod
def name(value: Union[int, 'TrajFlag']) -> str:
    """Return a concatenated name representation of the given flag value."""
    if value in _TrajFlagNames:
        return _TrajFlagNames[value]

    parts = [name for bit, name in _TrajFlagNames.items() if bit and (value & bit) == bit]
    if "ZERO_UP" in parts and "ZERO_DOWN" in parts:
        parts.remove("ZERO_UP")
        parts.remove("ZERO_DOWN")
    return "|".join(parts) if parts else "UNKNOWN"

DangerSpace

Bases: NamedTuple

Stores the danger space data for distance specified

overlay

overlay(ax: Axes, label: Optional[str] = None)

Highlights danger-space region on plot

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
162
163
164
165
166
167
168
169
170
def overlay(self, ax: 'Axes', label: Optional[str] = None):  # type: ignore
    """Highlights danger-space region on plot"""
    try:
        from py_ballisticcalc.visualize.plot import add_danger_space_overlay  # type: ignore
        add_danger_space_overlay(self, ax, label)
    except ImportError as err:
        raise ImportError(
            "Use `pip install py_ballisticcalc[charts]` to get results as a plot"
        ) from err

TrajectoryData

Bases: NamedTuple

Data for one point in ballistic trajectory

Attributes:

Name Type Description
time float

bullet flight time

distance Distance

x-axis coordinate

velocity Velocity

velocity

mach float

velocity in Mach prefer_units

height Distance

y-axis coordinate

target_drop Distance

drop relative to sight-line

drop_adj Angular

sight adjustment to zero target_drop at this distance

windage Distance
windage_adj Angular
look_distance Distance

sight-line distance = .distance/cosine(look_angle)

# look_height (Distance

y-coordinate of sight-line = .distance*tan(look_angle)

angle Angular

Angle of velocity vector relative to x-axis

density_factor float

Ratio of air density here to standard density

drag float

Current drag coefficient

energy Energy
ogw Weight

optimal game weight

flag int

row type

formatted

formatted() -> Tuple[str, ...]

:return: matrix of formatted strings for each value of trajectory in default prefer_units

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def formatted(self) -> Tuple[str, ...]:
    """
    :return: matrix of formatted strings for each value of trajectory in default prefer_units
    """

    def _fmt(v: AbstractDimension, u: Unit) -> str:
        """simple formatter"""
        return f"{v >> u:.{u.accuracy}f} {u.symbol}"

    return (
        f'{self.time:.3f} s',
        _fmt(self.distance, PreferredUnits.distance),
        _fmt(self.velocity, PreferredUnits.velocity),
        f'{self.mach:.2f} mach',
        _fmt(self.height, PreferredUnits.drop),
        _fmt(self.target_drop, PreferredUnits.drop),
        _fmt(self.drop_adj, PreferredUnits.adjustment),
        _fmt(self.windage, PreferredUnits.drop),
        _fmt(self.windage_adj, PreferredUnits.adjustment),
        _fmt(self.look_distance, PreferredUnits.distance),
        _fmt(self.angle, PreferredUnits.angular),
        f'{self.density_factor:.3e}',
        f'{self.drag:.3f}',
        _fmt(self.energy, PreferredUnits.energy),
        _fmt(self.ogw, PreferredUnits.ogw),

        TrajFlag.name(self.flag)
    )

in_def_units

in_def_units() -> Tuple[float, ...]

:return: matrix of floats of the trajectory in default prefer_units

Source code in py_ballisticcalc\trajectory_data\_trajectory_data.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def in_def_units(self) -> Tuple[float, ...]:
    """
    :return: matrix of floats of the trajectory in default prefer_units
    """
    return (
        self.time,
        self.distance >> PreferredUnits.distance,
        self.velocity >> PreferredUnits.velocity,
        self.mach,
        self.height >> PreferredUnits.drop,
        self.target_drop >> PreferredUnits.drop,
        self.drop_adj >> PreferredUnits.adjustment,
        self.windage >> PreferredUnits.drop,
        self.windage_adj >> PreferredUnits.adjustment,
        self.look_distance >> PreferredUnits.distance,
        self.angle >> PreferredUnits.angular,
        self.density_factor,
        self.drag,
        self.energy >> PreferredUnits.energy,
        self.ogw >> PreferredUnits.ogw,
        self.flag
    )