def __init__(self, target: str, time_unit: str = "min", angular_unit: str = "deg"): """ Creates a JanusMosaicGenerator :param target: target body, e.g. "CALLISTO" :param time_unit: Unit for temporal values - "sec", "min" or "hour" :param angular_unit: Unit for angular values - "deg", "rad", "arcMin" or "arcSec" """ if not target or not isinstance(target, str): raise TypeError( f"target must be a non-empty string, not '{target}'") self.target = target if time_unit not in time_units: raise ValueError( f"Time unit must be one of following: {time_units}") self.time_unit = time_unit if angular_unit not in angular_units: raise ValueError( f"Angular unit must be one of following: {angular_units}") self.angular_unit = angular_unit # Convert JANUS FOV size from radians to required angular unit self.fov_size: Tuple[float, float] = tuple( convertAngleFromTo(v_deg, "deg", self.angular_unit) for v_deg in self.JANUS_FOV_SIZE_DEG) # the time unit arguments are in opposite order because slew rate has time in denominator self.slew_rate_in_required_units = convertTimeFromTo( convertAngleFromTo(self.JUICE_SLEW_RATE_DEG_PER_SEC, "deg", self.angular_unit), self.time_unit, "sec")
def plot(self, query_spice: bool = True): """ Shows generated scan diagram. """ plt.figure() # plot rectangles for r in self.rectangles: r.plot_to_ax(plt.gca(), 'b') # plot slew trajectory traj_points = [] x_delta, y_delta = self.delta for i, cp in enumerate(self.center_points): tps = [(cp[0], cp[1] - y_delta / 2), (cp[0], cp[1] + y_delta / 2)] if i % 2: tps = list(reversed(tps)) traj_points += tps plt.gca().plot(*zip(*traj_points), 'k', linewidth=2, linestyle='dashed') plt.gca().plot(*zip(*traj_points), 'rx') if query_spice: radius_start = convertAngleFromTo( get_body_angular_diameter_rad("JUICE", self.target, self.start_time) / 2, "rad", self.angular_unit) circle_start = plt.Circle((0, 0), radius=radius_start, color='#FF0000', fill=False, linewidth=2) plt.gca().add_artist(circle_start) radius_end = convertAngleFromTo( get_body_angular_diameter_rad("JUICE", self.target, self.end_time) / 2, "rad", self.angular_unit) circle_end = plt.Circle((0, 0), radius=radius_end, color='#A00000', fill=False, linewidth=2, linestyle='-.') plt.gca().add_artist(circle_end) illuminated_shape_start = get_illuminated_shape( "JUICE", self.target, self.start_time, self.angular_unit) plt.gca().plot(*illuminated_shape_start.exterior.xy, '#CCCC00') illuminated_shape_end = get_illuminated_shape( "JUICE", self.target, self.end_time, self.angular_unit) plt.gca().plot(*illuminated_shape_end.exterior.xy, color='#999900', linestyle='-.') plt.axis('equal') plt.grid() plt.xlabel(f'X coordinate [{self.angular_unit}]') plt.ylabel(f'Y coordinate [{self.angular_unit}]') plt.title(f'Scan of {self.target} at {self.start_time.isoformat()}') plt.show()
def test_input(self): with self.assertRaises(ValueError, msg="Invalid unit request"): convertAngleFromTo(1.3, " min", "sec") with self.assertRaises(ValueError, msg="Invalid unit request"): convertAngleFromTo(1.3, "deg", "sec") with self.assertRaises(ValueError, msg="Invalid unit request"): convertAngleFromTo(1.3, "deg", "ArcSec") with self.assertRaises(ValueError, msg="Invalid unit request"): convertAngleFromTo(1.3, None, "arcSec") with self.assertRaises(ValueError, msg="Invalid unit request"): convertAngleFromTo(1.3, "deg", "")
def plot(self, query_spice: bool = True): """ Shows generated mosaic diagram. """ plt.figure() for r in self.rectangles: r.plot_to_ax(plt.gca(), 'b') plt.gca().plot(*zip(*self.center_points), 'k') plt.gca().plot(*zip(*self.center_points), 'rx') if query_spice: radius_start = convertAngleFromTo( get_body_angular_diameter_rad("JUICE", self.target, self.start_time) / 2, "rad", self.angular_unit) circle_start = plt.Circle((0, 0), radius=radius_start, color='#FF0000', fill=False, linewidth=2) plt.gca().add_artist(circle_start) radius_end = convertAngleFromTo( get_body_angular_diameter_rad("JUICE", self.target, self.end_time) / 2, "rad", self.angular_unit) circle_end = plt.Circle((0, 0), radius=radius_end, color='#A00000', fill=False, linewidth=2, linestyle='-.') plt.gca().add_artist(circle_end) illuminated_shape_start = get_illuminated_shape( "JUICE", self.target, self.start_time, self.angular_unit) plt.gca().plot(*illuminated_shape_start.exterior.xy, '#CCCC00') illuminated_shape_end = get_illuminated_shape( "JUICE", self.target, self.end_time, self.angular_unit) plt.gca().plot(*illuminated_shape_end.exterior.xy, color='#999900', linestyle='-.') plt.axis('equal') plt.grid() plt.xlabel(f'X coordinate [{self.angular_unit}]') plt.ylabel(f'Y coordinate [{self.angular_unit}]') plt.title(f'Mosaic of {self.target} at {self.start_time.isoformat()}') plt.show()
def test_conversions(self): self.assertEquals(convertAngleFromTo(0.0, "deg", "rad"), 0.0) self.assertEquals(convertAngleFromTo(0, "deg", "deg"), 0.0) self.assertEquals( convertAngleFromTo(0.23184984351384843, "deg", "deg"), 0.23184984351384843) self.assertEquals(convertAngleFromTo(-3.1689, "deg", "deg"), -3.1689) self.assertEquals(convertAngleFromTo(1.0, "deg", "rad"), np.pi / 180) self.assertAlmostEquals(convertAngleFromTo(4.36, "arcSec", "rad"), 2.114e-5, places=4) self.assertAlmostEquals(convertAngleFromTo(14.36, "arcSec", "arcMin"), 0.23933, places=4) self.assertAlmostEquals(convertAngleFromTo(814.21, "deg", "arcMin"), 48842.8, places=0)
def __init__(self, fov_width: float, probe: str, target: str, start_time: datetime, time_unit: str, angular_unit: str, measurement_slew_rate: float, transfer_slew_rate: float): """ Create a ScanGenerator :param fov_width: width of FOV along x-axis :param probe: SPICE name of probe, e.g. "JUICE" :param target: SPICE name of target body, e.g. "CALLISTO" :param start_time: Time of beginning of mosaic :param time_unit: Unit for temporal values - "sec", "min" or "hour" :param angular_unit: Unit for angular values - "deg", "rad", "arcMin" or "arcSec" :param measurement_slew_rate: Slew rate during scanning measurement - depends on instrument :param transfer_slew_rate: Slew rate for transferring in between scans - as high as possible """ if fov_width <= 0.0: raise ValueError(f"FOV width must be positive: {fov_width}") self.fov_width = fov_width self.probe = probe self.target = target self.start_time = start_time if time_unit not in time_units: raise ValueError( f"Time unit must be one of following: {time_units}") self.time_unit = time_unit if angular_unit not in angular_units: raise ValueError( f"Angular unit must be one of following: {angular_units}") self.angular_unit = angular_unit if measurement_slew_rate <= 0.0: raise ValueError( f"Measurement slew rate must be positive: {measurement_slew_rate}" ) self.measurement_slew_rate = measurement_slew_rate if transfer_slew_rate <= 0.0: raise ValueError( f"Transfer slew rate must be positive: {transfer_slew_rate}") self.transfer_slew_rate = transfer_slew_rate # calculate angular size of target at start time self.target_angular_diameter = convertAngleFromTo( get_body_angular_diameter_rad(self.probe, self.target, start_time), "rad", self.angular_unit)
def __init__(self, fov_size: Tuple[float, float], probe: str, target: str, start_time: datetime, time_unit: str, angular_unit: str, dwell_time: float, slew_rate: float): """ Create a MosaicGenerator :param fov_size: 2-tuple (x, y) containing rectangular FOV size :param probe: SPICE name of probe, e.g. "JUICE" :param target: SPICE name of target body, e.g. "CALLISTO" :param start_time: Time of beginning of mosaic :param time_unit: Unit for temporal values - "sec", "min" or "hour" :param angular_unit: Unit for angular values - "deg", "rad", "arcMin" or "arcSec" :param dwell_time: Dwell time at each mosaic point :param slew_rate: Rate of slew of spacecraft in units specified """ if any([x < 0.0 for x in fov_size]): raise ValueError(f"FOV size values must be non-negative: {fov_size}") self.fov_size = fov_size self.probe = probe self.target = target self.start_time = start_time if time_unit not in time_units: raise ValueError(f"Time unit must be one of following: {time_units}") self.time_unit = time_unit if angular_unit not in angular_units: raise ValueError(f"Angular unit must be one of following: {angular_units}") self.angular_unit = angular_unit if dwell_time < 0.0: raise ValueError(f"Dwell time must be non-negative: {dwell_time}") self.dwell_time = dwell_time if slew_rate <= 0.0: raise ValueError(f"Slew rate must be positive: {slew_rate}") self.slew_rate = slew_rate # calculate angular size of target at start time self.target_angular_diameter = convertAngleFromTo(get_body_angular_diameter_rad(self.probe, self.target, start_time), "rad", self.angular_unit)