upstage.motion package#

Submodules#

upstage.motion.cartesian_model module#

0A 3-D intersection model for cartesian motion in UPSTAGE along straight-line paths.

cartesian_linear_intersection(start: CartesianLocation, finish: CartesianLocation, speed: float, sensor_location: CartesianLocation, sensor_radius: float) tuple[list[CartesianLocation], list[float], list[str], float]#

Get the intersection of straight line motion and a sphere.

Parameters:
  • start (CartesianLocation) – the start location of the mover

  • finish (CartesianLocation) – the finish location of the mover

  • speed (float) – the speed of the mover (in STAGE units)

  • sensor_location (CartesianLocation) – the location of the sensor

  • sensor_radius (float) – the radius of the sensor (in STAGE units)

Returns:

_description_

Return type:

tuple[list[CartesianLocation], list[float], list[str], float]

ray_intersection(start: L, toward: L, center: L, radii: float | L, speed: float) tuple[list[L], list[float]]#

Ray intersection with ellispoid.

Parameters:
  • start (XY | XYZ) – Start position

  • toward (XY | XYZ) – Point being looked at

  • center (XY | XYZ) – Center of the ellipsoid

  • radii (float | XY | XYZ) – Radii of each dimension of ellipsoid

  • speed (float) – Speed of the particle moving from start to toward

Returns:

Intersecting positions and times.

Return type:

tuple[list[XY] | list[XYZ], list[float]]

upstage.motion.geodetic_model module#

A model of the geodetic earth and intersecting paths/spheres.

analytical_intersection(start: GeodeticLocation, finish: GeodeticLocation, speed: float, sensor_location: GeodeticLocation, sensor_radius: float) tuple[list[GeodeticLocation], list[float], list[str], float]#

Calculate the intersection of a great circle.

The circle is defined by start & finish and the sphere defined by sensor_location and sensor_radius.

This function mimics the above subdivide_intersection, but uses analytical equations that run much faster.

Requires:

UP.add_stage_variable(“distance_units”, …) UP.add_stage_variable(“altitude_units”, …)

Parameters:
  • start (GeodeticLocation) – the start location of the mover

  • finish (GeodeticLocation) – the finish location of the mover

  • speed (float) – the speed of the mover (in STAGE units)

  • sensor_location (GeodeticLocation) – the location of the sensor

  • sensor_radius (float) – the radius of the sensor (in STAGE units)

Returns:

intersections, times, types, path_time

Return type:

tuple[list[GeodeticLocation], list[float], list[str], float]

subdivide_intersection(start: GeodeticLocation, finish: GeodeticLocation, speed: float, sensor_location: GeodeticLocation, sensor_radius: float) tuple[list[GeodeticLocation], list[float], list[str], float]#

Numerical intersection calculation.

Requires:

UP.add_stage_variable(“intersection_model”, INTERSECTION_LOCATION_CALLABLE) UP.add_stage_variable(“distance_units”, …)

Parameters:
Returns:

intersections, times, types, path_time

Return type:

tuple[list[GeodeticLocation], list[float], list[str], float]

upstage.motion.great_circle_calcs module#

Great circle calculations.

These equations were largely adapted from https://edwilliams.org/avform147.htm, although most of them can be verified from a number of open resources around the web. The overall algorithms have been slightly modified to support UPSTAGE.

get_course_rad(point1: GeodeticLocation, point2: GeodeticLocation) float#

Get the course (in radians) between point1 and point2.

Parameters:
  • point1 – the starting point

  • point2 – the ending point

get_dist_rad(point1: GeodeticLocation, point2: GeodeticLocation) float#

Get the distance (in radians) between point1 and point2.

Parameters:
  • point1 – the starting point

  • point2 – the ending point

get_great_circle_points(pointA: GeodeticLocation, pointB: GeodeticLocation, pointD: GeodeticLocation, dist: float) tuple[list[tuple[float, float]], list[float]] | None#

Let points A and B define a great circle route and D be a third point.

Find the points on the great circle through A and B that lie a distance d from D, if they exist.

Parameters:
  • pointA – GeodeticLocation of start of great circle

  • pointB – GeodeticLocation of end of great circle

  • pointD – GeodeticLocation, third point of interest (the center of sphere)

  • dist – (float) distance from third to point to find intersection on great circle (radians)

get_pos_from_points_and_distance(point1: GeodeticLocation, point2: GeodeticLocation, dist: float) tuple[float, float]#

Get a position (lat, lon) given a starting position, ending position, and distance.

Parameters:
  • point1 – GeodeticLocation of start of great circle

  • point2 – GeodeticLocation of end of great circle

  • dist – (float) distance along great circle to find third point

returns [lat, lon]

upstage.motion.motion module#

This file contains a queueing motion manager for sensor/mover intersections.

class SensorMotionManager(intersection_model: Callable[[LOC_INPUT, LOC_INPUT, float, LOC_INPUT, float], tuple[list[LOC_INPUT], list[float], list[str], float]], debug: bool = False)#

Bases: UpstageBase

Schedules the interaction of moving and detectable entities against non-moving ‘sensors’.

Movable objects must be Actors with: 1. GeodeticLocationChangingState OR CartesianLocationChangingState 2. DetectabilityState

Sensor objects MUST implement these two methods: 1. entity_entered_range(mover) 2. entity_exited_range(mover)

The first is called when a mover enters the sensor’s visiblity. The second is called when a mover leaves the visibility or becomes undetectable.

The motion manager will learn about sensor objects with:

sensor_motion_manager.add_sensor(sensor_object, location, radius)

Where location is a location object found in upstage.data_types and radius is a distance in the units defined in upstage.STAGE.

add_sensor(sensor: SensorType, location_attr_name: str = 'location', radius_attr_name: str = 'radius') None#

Add a sensor to the motion manager.

Parameters:
  • sensor (SensorType) – The sensor object

  • location_attr_name (str, optional) – Name of the location attribute. Defaults to “location”.

  • radius_attr_name (str, optional) – Name of the radius attribute. Defaults to “radius”.

class SensorType(*args, **kwargs)#

Bases: Protocol

Protocol class for sensor typing.

entity_entered_range(entity: Any) None#

Entity enters range and does something.

entity_exited_range(entity: Any) None#

Entity exit range and does something.

upstage.motion.stepped_motion module#

This file contains a motion manager that does time-stepping.

class SteppedMotionManager(timestep: float, max_empty_events: int = 3, debug: bool = False)#

Bases: UpstageBase

Tests relative distances of objects with a location property.

Reports to “sensor” objects when something enters or exits a range.

Use this manager when the sensing entities are not static. If they are static, use SensorMotionManager.

Detectable objects and sensor objects must have an attribute that is a GeodeticLocationState OR CartesianLocationState

Detectable objects, if they aren’t Actors, could implement _get_detection_state() -> bool:` to allow this class to ignore them sometimes. The default way is to use a DetectabilityState on the actor.

Sensor objects MUST implement these two methods: 1. entity_entered_range(object) 2. entity_exited_range(object)

The first is called when an entity enters the sensor’s visiblity. The second is called when an entity leaves the visibility or becomes undetectable.

The sensor object CAN implement a method called detection_checker. That method takes the location of an object to detect and returns True/False.

The motion manager will learn about sensor objects with:

sensor_motion_manager.add_sensor(sensor_object, radius)

Where radius is a distance in the units defined in upstage.STAGE.

Simple usage: >>> manager = SteppedMotionManager(timestep=0.1) >>> UP.STAGE.motion_manager = manager >>> … >>> manager.add_sensor(binoculars, ‘vision_radius’) >>> manager.add_detectable(bird, ‘location’)

# TODO: Unify sensor and movable # TODO: Having only moving things be detectable/using _start_mover is easy, but this class lets us do static detection easier, so we may have to go about it differently. # TODO: Data structures for efficient distances

add_detectable(detectable: Actor, location_attr_name: str = 'location', new_rate: float | None = None) None#

Add an object that is detectable to the manager.

The object must have an attribute that performs distance calculations. See the class docstring for more.

Parameters:
  • detectable (Actor) – An object that has a location attribute

  • location_attr_name (str) – Name of the location attribute. Defaults to “location”.

  • new_rate (float | None) – Optional new rate for a detectable

  • faster ((if it needs)

  • likely) (most)

add_sensor(sensor: SensorType, radius_attr_name: str = 'radius', location_attr_name: str = 'location') None#

Add a sensor the motion manager.

Parameters:
  • sensor (SensorType) – The sensing object

  • radius_attr_name (str) – Radius attribute name. Defaults to “radius”.

  • location_attr_name (str) – Location attribute name. Defaults to “location”.

run() Generator[Event, None, None]#

Run the main stepped motion loop.

Yields:

Generator[SimpyEvent, None, None] – _description_

run_particular(rate: float, detectable: Actor) Generator[Event, None, None]#

Run detections against a single target at a faster rate.

Parameters:
  • rate (float) – Time rate to do detection checks.

  • detectable (Actor) – The actor to be detected by the known sensors.

Module contents#

Motion features for UPSTAGE.