Click or drag to resize

Using STK Components with Python

The Python package JPype supports integrating Python with Java libraries. This means that STK Components can be run using Python. This topic will show you how to setup and use STK Components from a Python IDE.

Why Use Python with STK Components?

By integrating STK Components with Python, you have full access to Java libraries, STK Components and Python modules within one program. Python is a readable, easy to use language, which translates when using JPype to interface with Java. JPype makes Python look like Java and vice versa, so programmers who are experienced in either language won't have a large learning curve. Since interfacing with Java classes is simplified, the Python code still looks like Java. JPype interfaces both the Java and Python virtual machines at a native level which allows direct memory access between Python and Java. This means that you will be able to use all the capabilities of STK Components with the data analysis and visualization tools native to Python.

Prerequisites

You must have the following prerequisites met to integrate STK Components with Python:

  • A valid STK Components license

  • JPype, version 0.7.5 or later, installed

  • Python, version 3.7 or later, installed

Getting Started

When you start your Python program, you will need to setup the connection to the Java Virtual Machine and load your STK Components license before starting your analysis. This section will show you the basics of setting up and using JPype with STK Components.

Launching Java Virtual Machine (JVM)

The following code snippet demonstrates how to set up the JVM. First load the JPype package and add the STK Components JAR files to the classpath. The wildcard notation will add all the .jar files in that directory to the JVM. It's important that this step is done before starting the JVM. You can check to see what files were added to the classpath with jpype.getClassPath(). Finally, check to see if the JVM is already started, and if it isn't, launch the JVM. The parameter convertStrings defines how strings are returned by JPype. By setting this to false, Java strings will not be converted to Python strings when returned from a method. The second parameter, "-ea", enables assertions. Any string arguments that start with a dash will be passed into the JVM as command line flags.

Python
import jpype

# add the class path, where jarFolderFilePath is the filepath to the folder that the jar files are in
classpath = jarFolderFilePath + "\*"
jpype.addClassPath(classpath)

if not jpype.isJVMStarted():
    jpype.startJVM('-ea', convertStrings = False )

Importing Packages and Classes

There are multiple ways to import packages and classes. JPype supports the standard Python syntax for importing classes for the following packages: java, com, org, gov, mil, net, and edu. You can import these packages with import [java package] and from [java package] import [java class].

For other packages and classes, like STK Components, you need to use the jpype.JPackage() method. For STK Components, only the root (agi) needs to be declared with JPackage. You can then alias classes and namespaces as variables using the full path to the class. For a given class, the package will be listed on its information page. For example, the Cartesian class is in the agi.foundation.coordinates package. You can alias the entire namespace in one line or alias the root and then the class from the stored variable. Creating instances of a class is as you would in Python since you don't need to declare an instance with the new keyword. However, once you have created an instance, you can use it and the member functions as you would in Java. The following code snippet demonstrates both ways to alias a class and then how to construct and use a class.

Python
Duration = jpype.JPackage('agi').foundation.time.Duration

agi = jpype.JPackage('agi')
TimeStandard = agi.foundation.time.TimeStandard

duration = Duration(5, 0.0, TimeStandard.getCoordinatedUniversalTime())
days = duration.getDays()

Configuring STK Components License

After setting up the JVM, you need to load your STK Components license file. The following code snippet shows how to add your license to your Python project.

Python
#licensePath is complete file path of STK Components license
with open(licensePath) as f:
    licenseFile = f.read()

agi = jpype.JPackage('agi')
agi.foundation.Licensing.activateLicense(licenseFile)

Shutting Down the JVM

At the end of the program, we want the JVM to shutdown to fully terminate the program. This happens automatically without any user input or commands. However, if you want to continue executing only Python code after the JVM is finished, you can call jpype.shutdownJVM(). After this, no Java objects can be called and an exception will be raised if you try to access them.

However, one of the known limitations of JPype is the JVM cannot be restarted after shutting it down. If you do explicitly shutdown the JVM and then try to start it up again, you will receive the following error: OSError: JVM cannot be restarted. This can be resolved by closing your Python IDE and then opening it again.

We recommend not explicitly shutting the JVM down and allowing it to shutdown automatically so that you can rerun your program without having to close and reopen your IDE each time. In a program, you can still call and run Python native code while the JVM is still running. This makes Python a great option to use with STK Components because you can make use of Python analysis packages (Numpy, Pandas, etc.) in parallel with your STK Components analysis.

Creating Interfaces

There are multiple classes throughout the STK Components libraries that make use of creating a Delegate. While programming in Java, you would be able to create the interface through the invoke() function or specifying the function exactly.

In Python, JPype has made it simple to implement an interface, and the following code snippet demonstrates how to create and implement a Java interface. This sample is a translation of the DoubleFunctionExplorer example on the Exploring Functions topic in the Programmer's Guide. This example assumes that you have setup the JVM and STK Components license as described above.

To create an interface, begin by creating a Python class with method names that match those in the Java interface. It's important that the parameters of each method matches those in the Java interface with the addition of self as the first parameter. Add the @JImplements() decorator to the class definition, where the first argument is the interface you are implementing. Then, add the @JOverride decorator to each method contained in the class. Finally, create the delegate using the .of() method. More information about this process can be found on this page of JPype's documentation.

Python
agi = jpype.JPackage('agi')
DoubleFunctionExplorer = agi.foundation.numericalmethods.DoubleFunctionExplorer

explorer = DoubleFunctionExplorer()
explorer.setFindAllCrossingsPrecisely(True)

from jpype import JImplements, JOverride

DoubleSimpleFunction = agi.foundation.numericalmethods.DoubleSimpleFunction
@JImplements(DoubleSimpleFunction.Function)
class DoubleSimpleFunctionImplementation:
    @JOverride
    def invoke(self,variable):
        return 2 * variable + 5

explorer.getFunctions().add(DoubleSimpleFunction.of(DoubleSimpleFunctionImplementation()), 10.0)

DoubleSampleSuggestionCallback = agi.foundation.numericalmethods.DoubleSampleSuggestionCallback
@JImplements(DoubleSampleSuggestionCallback.Function)
class DoubleSampleSuggestionCallbackImplementation:
    @JOverride
    def invoke(self, intervalStart, intervalStop, lastSample):
        return lastSample + 1.0

explorer.setSampleSuggestionCallback(DoubleSampleSuggestionCallback.of(DoubleSampleSuggestionCallbackImplementation()))

thresholdcrossings = []

EventHandler = agi.foundation.compatibility.EventHandler
@JImplements(EventHandler.Function)
class MyEventHandlerImplementation:
    @JOverride
    def invoke(self, sender, e):
        thresholdcrossings.append(e.getFinding().getCrossingVariable())

explorer.addThresholdCrossingFound(EventHandler.of(MyEventHandlerImplementation()))

explorer.explore(0.0, 10.0)
Debugging

Inevitably, you will have to do some debugging. This section will address some notes and hints that might be helpful.

Function Errors in Python

While JPype is usually good at implicitly converting parameters into their corresponding Java types, sometimes you could receive a TypeError: No matching overloads found error. You will be able to see in the console what you passed into a function and what the acceptable options are. For example, this line of code is creating an ImpulsiveManeuverInformation.

Python
maneuverInfo = ImpulsiveManeuverInformation("satellite", Cartesian(200.0, 0.0, 0.0), 
                                               "fuelMass", "dryMass",
                                               5000.0, InvalidFuelStateBehavior.THROW_EXCEPTION)

However, this line will throw the following error:

Constructor Error
TypeError: No matching overloads found for constructor agi.foundation.segmentpropagation.ImpulsiveManeuverInformation(str,agi.foundation.coordinates.Cartesian,str,str,float,agi.foundation.segmentpropagation.InvalidFuelStateBehavior), options are:
public agi.foundation.segmentpropagation.ImpulsiveManeuverInformation(java.lang.String,agi.foundation.coordinates.Cartesian,agi.foundation.geometry.Axes)
public agi.foundation.segmentpropagation.ImpulsiveManeuverInformation(java.lang.String,agi.foundation.coordinates.Cartesian,agi.foundation.geometry.Axes,java.lang.String,java.lang.String,agi.foundation.geometry.Scalar,agi.foundation.segmentpropagation.InvalidFuelStateBehavior)
public agi.foundation.segmentpropagation.ImpulsiveManeuverInformation(java.lang.String,agi.foundation.coordinates.Cartesian,java.lang.String,java.lang.String,agi.foundation.geometry.Scalar,agi.foundation.segmentpropagation.InvalidFuelStateBehavior)
public agi.foundation.segmentpropagation.ImpulsiveManeuverInformation(java.lang.String,agi.foundation.coordinates.Cartesian)
public agi.foundation.segmentpropagation.ImpulsiveManeuverInformation()

This is helpful because you can see specifically what needs to be converted or specified. In this case, JPype will convert the str to java.lang.String, but the the 5000 needs to be converted manually to a Scalar. The following line of code demonstrates how to fix this line:

Python
maneuverInfo = ImpulsiveManeuverInformation("satellite", Cartesian(200.0, 0.0, 0.0), 
                                               "fuelMass", "dryMass",
                                               Scalar.toScalar(5000.0), InvalidFuelStateBehavior.THROW_EXCEPTION)

Adding a Remote Debugger

If you need to drop down to a debugger, you can attach a debugger to your program. Start the JVM with arguments to tell it to enter a debugging session using the Java Debug Wire Protocol to setup a port for the debugger to attach to. Then, place a breakpoint to pause the Python program, attach the debugger, set a breakpoint in Java and then tell Python to proceed. There is more information on this process in JPype's documentation here

Tutorial
Note Note

The example script described in this topic requires a license for the Segment Propagation Library to run.

Additional Requirements for this Tutorial

  • Python IDE, such as Spyder, installed

  • Sample Python file located in the Examples\Python directory of the STK Components install

  • Matplotlib

  • Numpy

Description

We have created a tutorial on how to use STK Components with Python, which is located at Examples\Python\HohmannTransfer.py in your STK Components install. It goes through how to set up a Hohmann Transfer from an initial orbit with a apogee of 10,000 km and perigee of 7071 km to a final orbit with an eccentricity of 0.1 and apogee of 42,000 km. The script then goes into analyzing the generated ephemeris and creating graphs for fuel over time, altitude over time, speed over time and both a static and animated ground track.

Python Plots