SOD Inline Scripting
This tutorial takes you through inlining scripts in SOD. Each ingredient type has a corresponding script subsetter that allows customization of SOD's procssing via user written scripts. This can often be a very efficient way to customize your processing recipe beyond the features that come with SOD.
Inline Script Scructure
Inline Scripts in SOD have a simple, yet flexible, structure and are based on the JSR 223 Java Scripting API. Because of this, the particular scripting language is choosen by the user as long as the bindings for that language exist in a way that SOD can find them.
We will start with a simple example of a station subsetting script. This example can be found in the recipies directory of the SOD distribution. For this example, we will create a custom script that accepts stations at high elevation, greater than 1500 meters above sea level. While this is a relatively simple example, it will illustrate a couple of important ideas.
Language Choice
The structure of the
Two other languages that might be of interest to use for scripting subsetters are Groovy and JRuby. While jython, groovy and jruby are probably the three most common, any other language that supports JSR223 can be used.
Script Source
The second part of the scripting subsetters is the body of the tag, which contains the actual script source code. As far as SOD is concerned, this is just text, but this will be passed to the script engine for your language of choice, and so should be valid code for that language. For our example of checking the elevation of the station using Jython, the scripting element would look like this:
<stationScript type="jython"> if station.getWrapped().getLocation().elevation.value > 1500: print "%s %s\n"%(station.getWrapped().getLocation().elevation, station) result = True else: result = False </stationScript>
While this code is simple Python code, there are a few SOD specifics that you need to know in order to write a subsetters. The first is that for each subsetter type, several variables are initialized in order to pass the objects relavant to the subsetting. In this example, these variables are "station" and "networkSource". The same class, VelocityStation, is used here as is used in the templates for the printline processes. This has the advantage that there are several useful methods defined on this object, in particular for printing purposes. However, in many cases the raw StationImpl object is more useful when calculations are needed. This is accessed via the getWrapped() method. Reviewing the API documents for the velocity and Impl classes can be very useful in writting scripts. In this example, we simply check the elevation and print out a line if the elevation is above 1500.
The javadocs for each type of script subsetter are below. The variables passed into each script are the same as the arguments to the runScript method. So, for the Station script, two variables are presset in the script, station of type VelocityStation and networkSource of type VelocityNetworkSource.
- Network - <networkScript>
- Station - <stationScript>
- Channel - <channelScript>
- Origin - <originScript>
- Event Station - <eventStationScript>
- Event Channel - <eventChannelScript>
- Event Vector - <eventVectorScript>
- Request Generator - <requestGeneratorScript>
- Vector RequestGenerator - <vectorRequestGeneratorScript>
- Request - <requestScript>
- Vector Request - <vectorRequestScript>
- Available Data - <availableDataScript>
- Vector Available Data - <availableDataScript>
- Seismogram - <seismogramScript>
- Waveform Vector - <vectorScript>
Return Value
The other direction for data transfer is how the script returns the result to SOD. Unfortunately there are some differences in scripting languages. As a result, SOD checks the return value of the script but if that is not set then SOD pulls the variable named "result" from the script engine after execution. We use this technique in the example. The return value here is a simple boolean, true or false. The other choice is to return a StringTreeLeaf object, which contains both the boolean result and also an optional reason. This is mostly used in cases where you wish to explain failures. The reason will be put into the Fail logger which usually ends up in the file Fail.log.
One small problem can crop up due to the script being inlined in the xml recipe, the text of the script must conform to allowed xml. So for example if we wanted stations close to sea level instead, it would be tempting to do this:
if station.getWrapped().getLocation().elevation.value < 10:
However, this would not work as the XML processor would think that the less than sign indicated a new XML element. The workaround for this type of issue is to "ampersand encode" the less than sign. The XML processor will expand the < before the script sees it, and it will also be valid XML.
if station.getWrapped().getLocation().elevation.value < 10: