#
# ISC License
#
# Copyright (c) 2022, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
r"""
Overview
--------
This scenario demonstrates mapping several points on the surface of the Earth.
The script is found in the folder ``basilisk/examples`` and executed by using::
python3 scenarioGroundMapping.py
The simulation uses :ref:`groundMapping` to check if a spacecraft has access to any of the mapping points defined on the
surface of the Earth. For each mapping point, a :ref:`accessMsgPayload` is output to the :ref:`mappingInstrument` module,
which images the mapping points and sends the simulated data to a :ref:`partitionedStorageUnit`. While this is not
representative of the total amount of data collected during mapping, it does store which points have and have not been
mapped, which is useful for many operations problems. It is suggested that the user adds a :ref:`simpleInstrument` and
:ref:`simpleStorageUnit` if they desire to realistically capture how much data was actually collected.
Illustration of Simulation Results
----------------------------------
::
show_plots = True
The following plots illustrate the attitude error of the spacecraft, a 3D view of the spacecraft trajectory and the mapping
points expressed in the planet-centered, planet-fixed frame, and the number of points stored in the data buffer.
.. image:: /_images/Scenarios/scenarioGroundMapping1.svg
:align: center
.. image:: /_images/Scenarios/scenarioGroundMapping2.svg
:align: center
.. image:: /_images/Scenarios/scenarioGroundMapping3.svg
:align: center
"""
#
# Basilisk Scenario Script and Integrated Test
#
# Purpose: Integrated test of the groundMapping(), mappingInstrument(), partitionedStorageUnit() modules.
# Author: Adam Herrmann
# Creation Date: April 18th, 2022
#
import math as m
import os
import matplotlib.pyplot as plt
import numpy as np
# import message declarations
from Basilisk.architecture import messaging
from Basilisk.fswAlgorithms import locationPointing
# import FSW Algorithm related support
from Basilisk.fswAlgorithms import mrpFeedback
from Basilisk.simulation import ephemerisConverter
from Basilisk.simulation import extForceTorque
from Basilisk.simulation import groundMapping
from Basilisk.simulation import mappingInstrument
from Basilisk.simulation import partitionedStorageUnit
from Basilisk.simulation import simpleNav
# import simulation related support
from Basilisk.simulation import spacecraft
# import general simulation support files
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import astroFunctions
from Basilisk.utilities import macros
from Basilisk.utilities import orbitalMotion
from Basilisk.utilities import planetStates
from Basilisk.utilities import simIncludeGravBody
from Basilisk.utilities import (
unitTestSupport,
) # general support file with common unit test functions
# attempt to import vizard
from Basilisk.utilities import vizSupport
try:
from Basilisk.simulation import vizInterface
except ImportError:
pass
# The path to the location of Basilisk
# Used to get the location of supporting data.
from Basilisk import __path__
bskPath = __path__[0]
fileName = os.path.basename(os.path.splitext(__file__)[0])
# Plotting functions
[docs]
def plot_attitude_error(timeLineSet, dataSigmaBR):
"""Plot the attitude result."""
plt.figure(1)
fig = plt.gcf()
ax = fig.gca()
vectorData = dataSigmaBR
sNorm = np.array([np.linalg.norm(v) for v in vectorData])
plt.plot(
timeLineSet,
sNorm,
color=unitTestSupport.getLineColor(1, 3),
)
plt.xlabel("Time [min]")
plt.ylabel(r"Attitude Error Norm $|\sigma_{B/R}|$")
ax.set_yscale("log")
return
def plot_map_progress(mapping_points, map_access, sc_pos_PCPF):
fig = plt.figure(2)
ax = plt.axes(projection="3d")
ax.plot(
sc_pos_PCPF[:, 0] / 1000,
sc_pos_PCPF[:, 1] / 1000,
sc_pos_PCPF[:, 2] / 1000,
".",
)
ax.plot(
mapping_points[map_access, 0] / 1000,
mapping_points[map_access, 1] / 1000,
mapping_points[map_access, 2] / 1000,
".",
c="g",
)
ax.set_xlabel("X [km]")
ax.set_ylabel("Y [km]")
ax.set_zlabel("Z [km]")
return
def plot_stored_points(timeLineSet, stored_points):
fig = plt.figure(3)
plt.plot(timeLineSet, stored_points)
plt.xlabel("Time [min]")
plt.ylabel("Stored Points")
return
[docs]
def generate_mapping_points(num_points, radius):
"""Generates a number of mapping points on the surface of the body using a Fibonacci sphere
Algorithm from:
https://stackoverflow.com/questions/9600801/evenly-distributing-n-points-on-a-sphere
"""
points = []
phi = m.pi * (3.0 - m.sqrt(5.0)) # golden angle in radians
for i in range(num_points):
y = 1 - (i / float(num_points - 1)) * 2 # y goes from 1 to -1
r = m.sqrt(1 - y * y) # radius at y
theta = phi * i # golden angle increment
x = m.cos(theta) * r
z = m.sin(theta) * r
points.append(radius * np.array([x, y, z]))
return np.array(points)
[docs]
def run(show_plots, useCentral):
"""
The scenarios can be run with the followings setups parameters:
Args:
show_plots (bool): Determines if the script should display plots
useCentral (bool): Flag if the Earth is the central body or not
"""
# Create simulation variable names
simTaskName = "simTask"
simProcessName = "simProcess"
# Create a sim module as an empty container
scSim = SimulationBaseClass.SimBaseClass()
# Create the simulation process
dynProcess = scSim.CreateNewProcess(simProcessName)
# Create the dynamics task and specify the integration update time
simulationTimeStep = macros.sec2nano(5.0)
dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
# Initialize spacecraft object and set properties
scObject = spacecraft.Spacecraft()
scObject.ModelTag = "bsk-Sat"
# define the simulation inertia
I = [900.0, 0.0, 0.0, 0.0, 800.0, 0.0, 0.0, 0.0, 600.0]
scObject.hub.mHub = 750.0 # kg - spacecraft mass
scObject.hub.r_BcB_B = [
[0.0],
[0.0],
[0.0],
] # m - position vector of body-fixed point B relative to CM
scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I)
# Add spacecraft object to the simulation process
scSim.AddModelToTask(simTaskName, scObject)
# Clear prior gravitational body and SPICE setup definitions
gravFactory = simIncludeGravBody.gravBodyFactory()
# Setup Earth Gravity Body
earth = gravFactory.createEarth()
earth.isCentralBody = useCentral # ensure this is the central gravitational body
mu = earth.mu
timeInitString = '2020 MAY 21 18:28:03 (UTC)'
spiceObject = gravFactory.createSpiceInterface(time=timeInitString)
scSim.AddModelToTask(simTaskName, spiceObject, -1)
# Attach gravity model to spacecraft
gravFactory.addBodiesTo(scObject)
# setup the orbit using classical orbit elements
oe = orbitalMotion.ClassicElements()
oe.a = (6378 + 2000) * 1000.0 # meters
oe.e = 0.01
oe.i = 63.3 * macros.D2R
oe.Omega = 88.2 * macros.D2R
oe.omega = 347.8 * macros.D2R
oe.f = 135.3 * macros.D2R
rN, vN = orbitalMotion.elem2rv(mu, oe)
scObject.hub.r_CN_NInit = rN # m - r_CN_N
scObject.hub.v_CN_NInit = vN # m/s - v_CN_N
scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]] # sigma_BN_B
scObject.hub.omega_BN_BInit = [[0.001], [-0.01], [0.03]] # rad/s - omega_BN_B
if useCentral:
scObject.hub.r_CN_NInit = rN # m - r_BN_N
scObject.hub.v_CN_NInit = vN # m/s - v_BN_N
else:
planetPosition, planetVelocity = planetStates.planetPositionVelocity(
"EARTH", timeInitString
)
scObject.hub.r_CN_NInit = rN + np.array(planetPosition)
scObject.hub.v_CN_NInit = vN + np.array(planetVelocity)
# setup extForceTorque module
# the control torque is read in through the messaging system
extFTObject = extForceTorque.ExtForceTorque()
extFTObject.ModelTag = "externalDisturbance"
scObject.addDynamicEffector(extFTObject)
scSim.AddModelToTask(simTaskName, extFTObject, ModelPriority=95)
# add the simple Navigation sensor module. This sets the SC attitude, rate, position
# velocity navigation message
sNavObject = simpleNav.SimpleNav()
sNavObject.ModelTag = "SimpleNavigation"
scSim.AddModelToTask(simTaskName, sNavObject, ModelPriority=99)
sNavObject.scStateInMsg.subscribeTo(scObject.scStateOutMsg)
# Generate the mapping points
N = 500
mapping_points = generate_mapping_points(N, astroFunctions.E_radius * 1e3)
# Create the ground mapping module
groundMap = groundMapping.GroundMapping()
groundMap.ModelTag = "groundMapping"
for map_idx in range(N):
groundMap.addPointToModel(mapping_points[map_idx, :])
groundMap.minimumElevation = np.radians(45.0)
groundMap.maximumRange = 1e9
groundMap.cameraPos_B = [0, 0, 0]
groundMap.nHat_B = [0, 0, 1]
groundMap.halfFieldOfView = np.radians(22.5)
groundMap.scStateInMsg.subscribeTo(scObject.scStateOutMsg)
groundMap.planetInMsg.subscribeTo(spiceObject.planetStateOutMsgs[0])
scSim.AddModelToTask(simTaskName, groundMap, 1)
# Create the mapping instrument
mapInstrument = mappingInstrument.MappingInstrument()
mapInstrument.nodeBaudRate = 1
for map_idx in range(N):
mapInstrument.addMappingPoint(groundMap.accessOutMsgs[map_idx], str(map_idx))
scSim.AddModelToTask(simTaskName, mapInstrument, 2)
# Create the partitioned storage unit
storageUnit = partitionedStorageUnit.PartitionedStorageUnit()
storageUnit.ModelTag = "storageUnit"
storageUnit.storageCapacity = 8e9 # bits (1 GB)
for map_idx in range(N):
storageUnit.addDataNodeToModel(mapInstrument.dataNodeOutMsgs[map_idx])
storageUnit.addPartition(str(map_idx))
scSim.AddModelToTask(simTaskName, storageUnit, 3)
# Create the ephemeris converter module
ephemConverter = ephemerisConverter.EphemerisConverter()
ephemConverter.ModelTag = "ephemConverter"
ephemConverter.addSpiceInputMsg(spiceObject.planetStateOutMsgs[0])
scSim.AddModelToTask(simTaskName, ephemConverter)
# Setup nadir pointing guidance module
locPoint = locationPointing.locationPointing()
locPoint.ModelTag = "locPoint"
scSim.AddModelToTask(simTaskName, locPoint, ModelPriority=97)
locPoint.pHat_B = [0, 0, 1]
locPoint.scAttInMsg.subscribeTo(sNavObject.attOutMsg)
locPoint.scTransInMsg.subscribeTo(sNavObject.transOutMsg)
locPoint.celBodyInMsg.subscribeTo(ephemConverter.ephemOutMsgs[0])
# Setup the MRP Feedback control module
mrpControl = mrpFeedback.mrpFeedback()
mrpControl.ModelTag = "MRP_Feedback"
scSim.AddModelToTask(
simTaskName, mrpControl, ModelPriority=96
)
mrpControl.guidInMsg.subscribeTo(locPoint.attGuidOutMsg)
mrpControl.K = 5.5
mrpControl.Ki = -1 # make value negative to turn off integral feedback
mrpControl.P = 30.0
mrpControl.integralLimit = 2.0 / mrpControl.Ki * 0.1
# connect torque command to external torque effector
extFTObject.cmdTorqueInMsg.subscribeTo(mrpControl.cmdTorqueOutMsg)
# create the FSW vehicle configuration message
vehicleConfigOut = messaging.VehicleConfigMsgPayload()
vehicleConfigOut.ISCPntB_B = (
I # use the same inertia in the FSW algorithm as in the simulation
)
configDataMsg = messaging.VehicleConfigMsg().write(vehicleConfigOut)
mrpControl.vehConfigInMsg.subscribeTo(configDataMsg)
# Setup data logging before the simulation is initialized
mrpLog = mrpControl.cmdTorqueOutMsg.recorder()
attErrLog = locPoint.attGuidOutMsg.recorder()
snAttLog = sNavObject.attOutMsg.recorder()
snTransLog = sNavObject.transOutMsg.recorder()
scLog = scObject.scStateOutMsg.recorder()
planetLog = spiceObject.planetStateOutMsgs[0].recorder()
storageLog = storageUnit.storageUnitDataOutMsg.recorder()
# Setup the logging for the mapping locations
mapLog = []
for idx in range(0, N):
mapLog.append(groundMap.accessOutMsgs[idx].recorder())
scSim.AddModelToTask(simTaskName, mapLog[idx])
# Add all of the logs to the task
scSim.AddModelToTask(simTaskName, mrpLog)
scSim.AddModelToTask(simTaskName, attErrLog)
scSim.AddModelToTask(simTaskName, snAttLog)
scSim.AddModelToTask(simTaskName, snTransLog)
scSim.AddModelToTask(simTaskName, scLog)
scSim.AddModelToTask(simTaskName, planetLog)
scSim.AddModelToTask(simTaskName, storageLog)
# setup Vizard support
if vizSupport.vizFound:
genericSensorHUD = vizInterface.GenericSensor()
genericSensorHUD.r_SB_B = [0.0, 0.0, 0.0]
genericSensorHUD.fieldOfView.push_back(
45 * macros.D2R
) # single value means a conic sensor
genericSensorHUD.normalVector = [0.0, 0.0, 1.0]
genericSensorHUD.color = vizInterface.IntVector(
vizSupport.toRGBA255("red", alpha=0.25)
)
genericSensorHUD.label = "genSen1"
viz = vizSupport.enableUnityVisualization(
scSim,
simTaskName,
scObject
# , saveFile=fileName
,
genericSensorList=genericSensorHUD,
)
# the following command sets Viz settings for the first spacecraft in the simulation
vizSupport.setInstrumentGuiSetting(
viz,
showGenericSensorLabels=True,
)
# Need to call the self-init and cross-init methods
scSim.InitializeSimulation()
# Set the simulation time.
scSim.ConfigureStopTime(macros.hour2nano(1))
# Begin the simulation time run set above
scSim.ExecuteSimulation()
# Retrieve the logged data
dataSigmaBR = attErrLog.sigma_BR
sc_pos_N = scLog.r_BN_N
dcms_PN = planetLog.J20002Pfix
planet_pos_N = planetLog.PositionVector
timeLineSet = attErrLog.times() * macros.NANO2MIN
# Compute the PCPF position of the spacecraft
sc_pos_PCPF = np.zeros((len(sc_pos_N), 3))
for idx in range(0, len(sc_pos_N)):
sc_pos_PCPF[idx, :] = np.matmul(
dcms_PN[idx, :, :], sc_pos_N[idx, :] - planet_pos_N[idx, :]
)
# Retrieve the access messages
map_access = np.zeros(N, dtype=bool)
for idx in range(0, N):
access = mapLog[idx].hasAccess
if sum(access):
map_access[idx] = 1
storedData = storageLog.storedData
stored_points = np.zeros(len(timeLineSet))
for idx in range(0, len(timeLineSet)):
stored_points[idx] = np.count_nonzero(storedData[idx, :])
# Plot the results
plt.close("all") # clears out plots from earlier test runs
plot_attitude_error(timeLineSet, dataSigmaBR)
figureList = {}
pltName = fileName + "1"
figureList[pltName] = plt.figure(1)
plot_map_progress(mapping_points, map_access, sc_pos_PCPF)
pltName = fileName + "2"
figureList[pltName] = plt.figure(2)
plot_stored_points(timeLineSet, stored_points)
pltName = fileName + "3"
figureList[pltName] = plt.figure(3)
if show_plots:
plt.show()
# close the plots being saved off to avoid over-writing old and new figures
plt.close("all")
return figureList
#
# This statement below ensures that the unit test scrip can be run as a
# stand-along python script
#
if __name__ == "__main__":
run(True, True) # show_plots