.. _apiv1: APIv1 ***** .. contents:: :backlinks: none The Gaia Sky **APIv1** (`Javadoc `__) contains many calls to interact with the platform in real time from Python scripts or a REST HTTP server. The API includes calls to: - Add and remove messages and images to the interface, - start and stop time, and change the time warp, - add scene elements like shapes, lines, etc., - load full datasets in VOTable, CSV, FITS, or the internal JSON format, - manage datasets (highlight, change settings, etc.), - manipulate the camera position, orientation and mode, - move the camera by simulating mouse actions (rotate around, forward, etc.), - activate special modes like planetarium or panorama, - create smooth camera transitions in position and orientation, - change the various settings and preferences, - back-up and restore the full configuration state, - take screenshots, use the frame output mode. Gaia Sky provides a REST server that enables the remote execution of API calls over HTTP. This is described in :ref:`the REST server section `. APIv1 reference =============== The only up-to-date APIv1 documentation for each version is in the interface header files themselves. Below is a list of links to the different APIs. - `Latest API version `__ - `Older API versions (javadoc) `__. Scripting with APIv1 ==================== First of all, :ref:`install the environment ` if you have not done so. Gaia Sky uses the `single-threaded model `__ of Py4J. In order to connect to Gaia Sky from Python, import ``ClientServer`` and ``JavaParameters``, and then create a gateway and get its entry point. The entry point is the object you can use to call API methods on. Since Gaia Sky uses a server per script, the gateway must be shut down at the end of the script so that the Python program can terminate correctly and Gaia Sky can create a new server to deal with further scripts listening to the Py4J port. .. code:: python from py4j.clientserver import ClientServer, JavaParameters gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) gs = gateway.entry_point # User code goes here [...] gateway.shutdown() The ``JavaParameters(auto_convert=True)`` is not strictly necessary, but if you don't use it you need to convert Python lists to Java arrays yourself before calling the APIv1. Now, we can start calling APIv1 methods on the object ``gs``. In the following code snippet we call ``disableInput()``, ``cameraStop()``, ``setHeadlineMessage(String)``, and ``setSubheadMessage(String)``. .. code:: python # Disable input gs.disableInput() # Stop camera gs.cameraStop() # Write welcome message gs.setHeadlineMessage("Welcome to the Gaia Sky") gs.setSubheadMessage("Explore Gaia, the Solar System and the whole Galaxy!") [...] Find lots of example scripts `here `__. Backing up and restoring settings --------------------------------- Typically, scripts modify various program settings when they run (camera speed, star brightness, field of view, etc.). In order to leave Gaia Sky in the state it was before, scripts have the option to back up and restore the entire settings state of Gaia Sky. To do that, the APIv1 includes a few calls to push and pull settings states from an internal LIFO stack: - `backupSettings()` --- push the current settings state to the settings stack. - `restoreSettings()` --- restore the top-most settings state from the settings stack so that they become immediately effective. This call re-initializes the user interface of Gaia Sky, so be aware that the UI will be reset. - `clearSettingsStack()` --- clears the settings stack. Calling `restoreSettings()` after this will have no effect. These calls can be used at the start and end of scripts to back up and restore the user settings, so that everything is left unchanged after a script execution. .. code:: python from py4j.clientserver import ClientServer, JavaParameters gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) gs = gateway.entry_point # 1. Back up settings before anything gs.backupSettings() # 2. Script does things and modifies the settings [...] # 3. Restore the settings backed up at point 1. gs.restoreSettings() gateway.shutdown() Logging to Gaia Sky and Python ------------------------------ When printing messages, you can either log to Gaia Sky or print to the standard output of the terminal where Python runs: .. code:: python gs.print("This goes to the Gaia Sky log") print("This goes to the Python output") In order to log messages to both outputs, you can define a function which takes a string and prints it out to both sides: .. code:: python def pprint(text): gs.print(text) print(text) pprint("Hey, this is printed in both Gaia Sky AND Python!") Method and attribute access --------------------------- Py4J allows accessing public class methods but not public attributes. In case you get objects from Gaia Sky, you can't directly call public attributes, but need to access them via public methods: .. code:: python # Get the Mars model object body = gs.getObject("Mars") # Get spherical coordinates radec = body.getPosSph() # DO NOT do this, it crashes! gs.print("RA/DEC: %f / %f" % (radec.x, radec.y)) # DO THIS instead gs.print("RA/DEC: %f / %f" % (radec.x(), radec.y())) Strict parameter types ---------------------- As general advice, it is better to be strict with the parameter types. Use a floating point number when the method signature takes in a ``float`` or ``double``, and integers when it takes in an ``int`` or ``long``. The scripting interface still tries to perform conversions under the hood but it is better to do it right from the beginning. For example, the APIv1 method .. code:: java double[] galacticToInternalCartesian(double l, double b, double r); may not work if called like this from Python: .. code:: python gs.galacticToInternalCartesian(10, 43.5, 2) Note that the first and third parameters are integers rather than floating point numbers. Call it like this instead: .. code:: python gs.galacticToInternalCartesian(10.0, 43.5, 2.0) .. _scripting-datasets: Loading datasets from scripts ----------------------------- Gaia Sky supports data loading from scripts using the :ref:`STIL data provider `. It is really easy to load a VOTable file from a script: .. code:: python from py4j.clientserver import ClientServer, JavaParameters gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) gs = gateway.entry_point # Load dataset gs.loadDataset("dataset-name", "/path/to/dataset.vot") # Async insertion, let's make sure the data is available gs.sleep(2) # Now we can play around with it gs.hideDataset("dataset-name") # Show it again gs.showDataset("dataset-name") # Shutdown gateway.shutdown() Find an example of how to load a star catalog from a script `here `__. `This one `__ showcases how to load a dataset with generic particles (only positions). Additionally, you can also load JSON data files and dataset descriptors made for Gaia Sky (see the :ref:`JSON dataset format ` section). .. _transitions: Camera transitions ------------------ When writing scripts it is important to be able to transition the camera from one state to another. The camera state is composed by the camera position and the orientation (direction and up vectors). In the APIv1, we include a family of calls named ``cameraTransition(...)`` (see `here `__), which produce a transition from the current camera state, to the given camera position and orientation, in a given number of seconds (optionally different for position and orientation). Additionally, two more calls are available to create transitions only in position and only in orientation: - `cameraTransition(pos, units, dir, up, durationPos, smoothTypePos, smoothFactorPos, durationOri, smoothTypeOri, smoothFactorOri, sync) `__. - `cameraPositionTransition(pos, units, duration, smoothType, smoothFactor, sync) `__. - `cameraOrientationTransition(dir, up, duration, smoothType, smoothFactor, sync) `__. For the rest of this subsection, we refer to the base ``cameraTransition(...)`` method. Typically, when the transition must traverse large dynamic ranges of distances, it is necessary to smooth the transitions by starting slow and finishing slow, or starting fast and finishing fast. To that effect, we have included a sub-family of calls which include a smoothing type and factor for position and orientation. The transition duration is also separated by position and orientation. * APIv1 call: `cameraTransition(pos, units, dir, up, posDuration, posSmoothType, posSmoothFac, oriDuration, oriSmoothType, oriSmoothFac, sync) `__. **Duration** --- ``posDuration`` and ``oriDuration`` are in seconds, and specify the duration for the interpolation in position and orientation, respectively. They may be different, but the call will not return until the longest of the two has finished (if sync is ``true``). **Smoothing type** --- ``posSmoothType`` and ``orientationSmoothType`` determine the smoothing type. There are two types: **logistic sigmoid** and **logit** (additionally, **none** skips the smoothing). The logistic sigmoid type starts and ends slow, while the logit type starts and ends fast. Check out `this Graphtoy simulation `__. In it, :math:`f_2` is the logistic sigmoid (yellow), and :math:`f_3` is the logit type (green). .. figure:: img/figures/smoothing-types.jpg :alt: Smoothing types logistic sigmoid (in yellow) and logit (in green). :align: center Smoothing types logistic sigmoid (in yellow) and logit (in green). The full transition path is mapped to x:[0,1], and we use y:[0,1] given by the smoothing function to generate the sampling. **Smoothing factor** --- ``posSmoothFactor`` and ``orientationSmoothFactor`` determine the smoothing factor. In **logistic sigmoid** the factor must be in [12, Inf]. In **logit**, the factor is in [0.09, 0.01]. You can use the Graphtoy utility above to see the effect of the different factors. You can see and modify the expressions in the text fields to the top-right. You can always find the **target camera state** values by putting the camera in the end position and orientation in Gaia Sky, and running the ``get-cam-pos.py`` script (under ``assets/scripts/tests/``): .. code:: sh $ python get-cam-pos.py Camera position: - Internal: [-5593.0417731364, 13008.1430225486, 1542.9688571213] - Km: [-5593041773.1363697052, 13008143022.5486202240, 1542968857.1212708950] - AU: [-37.3871749360, 86.9540651588, 10.3141097317] - Light years: [-0.0005911850, 0.0013749619, 0.0001630919] - Parsecs: [-0.0001812581, 0.0004215652, 0.0000500042] Camera orientation: - Direction: [0.3965101844, -0.9104556836, -0.1176865406] - Up: [0.7060462888, 0.2205005155, 0.6729622283] Then, you can create a transition from the current camera state, to the target camera state. Here we have used internal units (first data line in the above snippet). .. code:: python gs.cameraTransition([-5593.0417731364, 13008.1430225486, 1542.9688571213], # Position "internal", # Units [0.3965101844, -0.9104556836, -0.1176865406], # Direction [0.7060462888, 0.2205005155, 0.6729622283], # Up 10.0, # Transition duration in position [s] "logisticsigmoid", # Smoothing type in position 60.0, # Smoothing factor in position 7.0, # Transition duration in orientation [s] "logisticsigmoid", # Smoothing type in orientation 12.0, # Smoothing factor in orientation True # Sync ) Field of view transitions ~~~~~~~~~~~~~~~~~~~~~~~~~ Additionally to the camera transitions, the family of calls ``fovTransition(...)`` (see `here `__) may be used to create smooth transitions in the camera field of view angle. They work in the same way as the :ref:`transitions`. FoV transitions typically happen between the current camera FoV and a given target value. ``duration``, ``smoothType``, and ``smoothFactor`` have the same meaning as in regular camera transitions. .. _sync-runnables: Synchronizing with the main loop -------------------------------- Sometimes, when updating animations or creating camera paths, it is necessary to sync the execution of scripts with the thread which runs the main loop (main thread). However, the scripting engine runs scripts in separate threads asynchronously, making it a non-obvious task to achieve this synchronization. In order to fix this, a new mechanism has been added in Gaia Sky ``2.0.3``. Now, runnables can be parked so that they run at the end of the update-render processing of each loop cycle. A runnable is a class which extends ``java.lang.Runnable``, and implements a very simple ``public void run()`` method. Runnables can be **posted**, meaning that they are run only once at the end fo the current cycle, or **parked**, meaning that they run until they stop or they are unparked. Parked runnables must provide a name identifier in order to be later accessed and unparked. Let's see an example of how to implement a frame counter in Python using ``py4j``: .. code:: python from py4j.clientserver import ClientServer, JavaParameters, PythonParameters class FrameCounterRunnable(object): def __init__(self): self.n = 0 def run(self): self.n = self.n + 1 if self.n % 30 == 0: gs.print("Number of frames: %d" % self.n) class Java: implements = ["java.lang.Runnable"] gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True), python_parameters=PythonParameters()) gs = gateway.entry_point # We park a runnable which counts the frames and prints the current number # of frames every 30 of them gs.parkRunnable("frame_counter", FrameCounterRunnable()) gs.sleep(15.0) # We unpark the frame counter gs.unparkRunnable("frame_counter") gateway.shutdown() In this example, we park a runnable which counts frames for 15 seconds. Note that here we need to pass a ``PythonParameters`` instance to the ``ClientServer`` constructor. A more useful example can be found `here `__. In this one, a polyline is created between the Earth and the Moon. Then, a parked runnable is used to update the line points with the new positions of the bodies. Finally, time is started so that the bodies start moving and the line positions are updated correctly and in synch with the main thread. Camera and scene runnables -------------------------- The Gaia Sky main loop updates first the camera position and orientation, and then updates the objects in the scene. In order to maintain sufficient precision, the scene is floated at the position of the camera, meaning that the camera is always effectively at the origin of coordinates, and the scene objects are moved around. This means that the effective position of every objects in the scene at every frame depends on the position of the camera. So far, we have seen the ``parkRunnable()`` method, which parks a runnable that runs only after the camera-scene update cycle. However, sometimes we need to modify the positions of objects in the scene with respect to other objects. If we use the current method, we will always be using the position in the last frame. However, we need to use the position in the current frame, and we can do so by introducing two new park methods: - ``parkCameraRunnable()`` --- parks a runnable that runs after the camera has updated, but **before** the scene has done so. Use this to fetch the **predicted** position of an object to have the position in the current frame. (see ``fetchPredictedPosition()``). - ``parkSceneRunnable()`` --- parks a runnable that runs after the camera and scene have updated. This is exactly the same as the ``parkRunnable()`` we already know. An example of this can be found `here `__. It needs a couple of JSON data files (also in the repository). .. _coord-provider-scripting: .. raw:: html
Since 3.5.0
Overriding object coordinates provider -------------------------------------- The positions of most objects in Gaia Sky are computed internally using coordinate providers. It is possible to override the coordinate providers of objects and implement your own in Python. This way of setting the position of an object is the best way to ensure internal consistency and overall system stability. When the coordinates provider is overriden, the user code runs naturally during the scene graph update stage. To implement a coordinates provider and set it to an object, you first need to create a class that implements ``IPythonCoordinatesProvider``, and submit it to Gaia Sky via the APIv1 call ``setObjectCoordinatesProvider(name, provider)``. The provider class needs to have a ``getEquatorialCartesianCoordinates(self, julianDate, outVector)`` method, which you need to implement. In it, you need to compute the coordinates of your object for the given Julian date (double-precision floating point number), in the :ref:`internal reference system and units `, and put the result in ``outVector``, using the method ``outVector.set(x, y, z)``. Here is an example: .. code:: python from py4j.clientserver import ClientServer, JavaParameters, PythonParameters from py4j.java_collections import ListConverter import os # This is the coordinates provider class. # It implements the method getEquatorialCartesianCoordinates(). class MyCoordinatesProvider(object): def __init__(self, gateway): self.gateway = gateway self.gs = gateway.entry_point self.converter = ListConverter() self.km_to_u = self.gs.kilometresToInternalUnits(1.0) self.pc_to_u = self.gs.parsecsToInternalUnits(1.0) def getEquatorialCartesianCoordinates(self, julianDate, outVector): # Here we need internal coordinates. x_km = 150000000 * self.km_to_u z_km = 200000000 * self.km_to_u v = [x_km, (julianDate - 2460048.0) * 100.0, z_km] # We need to set the result in the out vector. outVector.set(v[0], v[1], v[2]) return outVector def toString(): return "my-coordinates-provider" class Java: implements = ["gaiasky.util.coord.IPythonCoordinatesProvider"] gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True), python_parameters=PythonParameters()) gs = gateway.entry_point # Load test star system. gs.loadDataset("Test star system", os.path.abspath("./particles-body-coordinates.json")) # Set coordinates provider. provider = MyCoordinatesProvider(gateway) gs.setObjectCoordinatesProvider("Test Coord Star", provider) gs.startSimulationTime() gs.setCameraFocus("Test Coord Star") print("Coordinates provider set.") input("Press a key to finish...") gs.stopSimulationTime() # Clean up before shutting down, otherwise Gaia Sky will crash # due to the closed connection. gs.removeObjectCoordinatesProvider("Test Coord Star") gs.removeDataset("Coordinates test system") gs.sleep(2.0) gateway.shutdown() You can find this script, along with the necessary JSON data file, `here `__. More examples ------------- As we said, you can find more examples in the ``scripts`` `folder `__ in the repository.