6  Scripting

6.1 Concepts of scripting in iLand

The scripting approach used in iLand relies on the scripting capabilities of the Qt-framework. Qt includes a fully ECMA compatible JavaScript engine, which means that the JavaScript capabilities of iLand are very similar to those of a modern browser. While execution speed of scripts have been heavily optimized over the years (e.g., they are compiled just-in-time by the system), there is still a performance gap between the world of JavaScript and the native C++ world of iLand.

6.2 Introduction to JavaScript

JavaScript is a versatile programming language known for its adaptability and widespread usage in various applications beyond just web development. It’s like a Swiss Army knife for coding, enabling you to create interactive and dynamic content, not just for websites, but also for other tasks like data analysis, automation, and simulations.

Its user-friendly nature and extensive online resources make it relatively easy to learn, especially if you’re already familiar with languages like R. Additionally, JavaScript has a large community of developers who constantly contribute to its development, ensuring that there are plenty of libraries and frameworks available to simplify complex tasks.

Since JavaScript is mostly used as the “language of the web” (e.g., for dynamic web sites) a lot of documentation and discussions on sites such as StackOverflow are web-centered. But do not despair: there is plenty information on “pure” JavaScript available. As a rule of thumb, look for content that does not mention typical web “frameworks” such as “React JS”, “Angular”, “jQuery”. However, many sites about web development do have good information on “pure” Javascript!

Note

The JavaScript implementation used in iLand supports the full ECMA-262 specification - this means that all of the standard objects and functions are available (see https://doc.qt.io/qt-6/qtqml-javascript-functionlist.html for a full list). Also JavaScript libraries can be used - as long as they do not require browser-specific functionality.

These sites (among many others) provide good documentation about pure JavaScript:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference, or https://www.w3schools.com/jsref/

6.3 JavaScript in iLand

When you run JavaScript code in iLand (we’ll get to how to get code into the model in a second), then the code can access objects and functions that are provided by the model. These “links” to the model via objects and functions comprises the Application Programming Interface (API) of iLand. The documentation of the iLand API is at iland-model.org/apidoc. Code can be executed manually, or run automatically when the model runs (e.g., to execute forest management).

There is a single JavaScript “engine” in the model, which means that all JavaScript objects and functions share the same environment. As a consequence, elements can be overwritten! For instance, you could manually change a function that has been loaded previously; it also means you can only have one instance of an event handler (see Section 6.3.2.4). While a basic JavaScript engine is already available after startup of the model executable, iLand API objects are only available when a project is (successfully) loaded.

6.3.1 Ways to load JavaScript code

There are multiple options to load and execute JavaScript code in iLand. Note, that all JavaScript code shares the same execution environment!

6.3.1.1 Specify a code file in system.javascript.filename

iLand loads this JavaScript code file early on, and looks in the directory specified with system.path.scripts in the project file.

6.3.1.2 Specify a code file with a specific iLand sub module

Several sub modules of iLand allow specification of a dedicated JavaScript file. Examples are base management, ABE (agent based management engine), BITE (biotic disturbance engine), and some specialized outputs (e.g., the Stand Development Stage).

6.3.1.3 Write and run code manually in the iLand user interface

Code that is executed in the iLand JS code editor (see Figure 6.1) affects the JavaScript environment, i.e. you can create objects and functions, and run code. You can also run code that affects the vegetation, for example forest management scripts. Note that changes (e.g., harvested trees) may not be visible in the user interface immediately. You might need to refresh the visualization (F5 and F6 keys, see also the View menu), or even run an additional year of the simulation.

The code editor keeps the content of the editor area across iLand simulations, but does not execute any code automatically, so you’ll have to do it manually (usually by selecting the lines and pressing Ctrl+Enter key combination).

6.3.2 When and how is JavaScript code executed?

Generally, JavaScript code is executed as soon as it is loaded. This means that public declarations and code (that is, code that is not part of a function or an object) are executed immediately.

Listing 6.1
// global variables are initialized immediately
var my_var = "global";

// public code is also run immediately
console.log(my_var); // this prints "global" to the log file / screen

// this can be problematic, because the model may not be fully available
// note also that the "management" interface object is only available when
// iLand management module is enabled.
var n_trees = management.loadAll();
console.log("global: total number of trees: " + n_trees);

// functions are created, but *not* executed!
function check_trees() {
  
  let n_trees = management.loadAll();
  console.log("function: total number of trees: " + n_trees);
  
}

In Listing 6.1 we have global variables and code, as well as functions.

However, in most cases your JavaScript code will be called (i.e., run or invoked) by the model. iLand provides several ways for your scripts to be engaged by iLand. The most important are described below.

6.3.2.1 JavaScript in forest management and BITE

The implementation of forest management in iLand relies heavily on JavaScript. There are two flavors of management available: “base” management and ABE, the agent based management approach. The “base” management module is simpler (and older) than the agent based management approach, but still versatile and allows a wide range of options for altering the forest in the model (extracting of trees, planting of trees, …).

In base management, a single javascript file is defined in the Project file and loaded during startup of the model. Every year the function manage() is called by iLand. The example shows a very simple and not particularly useful management routine:

function manage(year) {
  console.log('management called in year ' + year); 
  
}

In case of ABE and BITE (a general module to simulate a wide range of biotic disturbances in forests) the integration with JavaScript is more powerful (but also more complex). Both modules rely on a declarative style (Section 6.3.4), and provide multiple ways to integrate your JavaScript code with objects of the iLand world provided via an API. See also the chapter on forest management for more details!

6.3.2.2 user interface

As mentioned previously, the user interface includes a simple “JavaScript code editor” that allows calling of JavaScript functions (Figure 6.1). In addition, the user can load JavaScript source from the iLand user interface. 

6.3.2.3 time events

iLand provides a mechanism that updates the values of model settings automatically over time. Typical uses are to trigger pre-defined events such as storms, or change CO2 concentration over time. The same mechanism can also be used to run JavaScript functions at specific times.

6.3.2.4 event handler

An event handler in the context of iLand is a JavaScript function that is called when a specific event happens during the simulation. If no such function is found, then nothing happens. In other words: to “enable” such a handler, you just need to define a function with the correct name. The following event handlers are available in every iLand simulation:

Handler Description
onAfterCreate Called immediately before the first simulation year is executed. 
onBeforeDestroy Called after the simulation has ended and the model is shut down.
onYearBegin Called at the beginning of each simulation year.
onYearEnd Called after each simulated year.
// examples for event handler code
// this can be part of any javascript code

function onAfterCreate() {
  console.log("after create event handler called. Counting trees...");
  
  // call another function (this one was defined in the above listing  -
  // now is a good time to actually execute it, 
  // as all trees are already present)
  check_trees();

}

function onYearEnd() {
  // the function is called every year, but it is simple
  // to limit actions to specific years:
  if (Globals.year == 100) {
    // do someting special in simulation year 100
    // e.g., create some very specific outputs
    
    // or just enable/disable existing outputs
    
    // or save a screenshot to a PNG file
    
  }
}

In addition, this mechanism is used in the fire module to allow some evaluation work after a fire happened.

6.3.3 Working with JavaScript in iLand

The user interface of iLand has rudimentary support for editing code and interacting with the model using JavaScript. While very useful on its own, iLand is not - and does not want to be - a full-fledged Integrated Development Environment (IDE) such as R Studio, or Visual Studio. If you are working on longer scripts, it can be a good approach to edit code using a good editor (Notepad++) or an IDE, and use iLand to inspect data and develop code snippets interactively.

Figure 6.1: Code editor and JavaScript workspace window

Using the code editor is straightforward. You can enter / copy&paste code, and run code by selecting the code and pressing Ctrl+Enter. To run code in a single line, it suffices to have the cursor in the line and press Ctrl+Enter. iLand “remembers” the content of the code editor between restarts, which makes it easy to keep some useful code snippets around. The executed code as well as any log messages from the code are shown in the “log output window”, even when iLand log output is written to a log file. If you are interested in the return value of executed code, you need to explicitly log (e.g., using console.log() or Globals.alert()), or store to a variable for later use. Note that it is possible to “overwrite” already existing functions / objects and run them interactively (see example below) - this is a great way to quickly improve or debug code without having to restart the model multiple times.

The “workspace” window allows inspection of variables and objects that are currently available in the JavaScript engine as pairs of “name” and “value”. The “name” is typically used to access the respective value. The following types of data are shown:

  • The “value” of simple types (such as numbers or strings) is shown directly

  • JavaScript objects (also nested objects) are shown as a tree of properties/functions, which can be expanded. Arrays of values or arrays of objects are also expandable. The “value” of objects is the generic label object Objects.

  • Objects provided by iLand API are shown as well. In this case the value is the internal object type name (sometimes slightly cryptic) and the (hexadecimal) memory address of said object (the memory address can be useful to decide if multiple Javascript object refer to the same underlying object in iLand memory space). An example is ABE::SchedulerObj(0x25d432a0).

  • Javascript functions are shown as function <functionname>() {[ native code ]}.

Note that iLand hides some standard objects to avoid cluttering (Math, JSON, Atomics, Reflect, console).

The following example shows how you access (and modify) elements shown in the workspace window:

// simple values
console.log(filename); 

// javascript objects

// iLand API
Globals.year = 23; // yields an error, as Globals.year is read-only

6.3.3.1 Output

Text output from JavaScript code is typically written to the log window (or the log file during simulations). A useful pattern that allows many details in the log during development, but avoids millions of log lines during large simulations, is the idea of “debug levels”. A simple implementation in JavaScript with just two levels (on or off) could look like this:

// global switch that turns on or off detailed logging
var debug_logging = true; 
// log function
function log(msg){
  if (debug_logging)
    console.log(msg);
}

// in user code:
var n = trees.count();
log(`there are ${n} trees in the list`);

You could have debug_logging set to true during development, or during debugging (just run debug_logging=true; in the editor to turn logging on), and switched off again later.

6.3.3.2 interactive development example

For example, consider the case when you want to create custom raster output files at certain years and have the code in a file that is automatically loaded (system.javascript.filename).

Listing 6.2
// content of "extra_output.js"
function onYearEnd() {
  if (Globals.year == 100) {
    writeExtraOutput();
  }
}

function writeExtraOutput() {
  // construct a unique name for the output file by using a user-defined 
  // scenario and the current year
  var filename = "output/extra_grid_" + 
    Globals.setting("user.code") + "_" +  Globals.year + ".asc";
  var vol_grid = Globals.resourceUnitGrid("basalArea");
  // basal area m2 of Norway spruce
  var species_grid = Globals.speciesShareGrid("piab"); 
  // calculate relative basal area for norway spruce
  vol_grid.combine('bP/bT', {bP: species_grid, bT: vol_grid} );
  vol_grid.save(filename); // and save to file
  
}

The code in Listing 6.2 shows:

  • how you can create unique filenames by using the current year of a simulation and values from the project file (the section user is dedicated for that use, as it is not used by the model itself).

  • how you can get gridded information from iLand, and can use that for simple “raster algebra” operations (see iLand API)

To test / improve your code, you could either run iLand multiple times (and wait until 100 years are simulated each time), or you do it interactively. You could:

  • copy the code snippet (the writeExtraOutput() function in Listing 6.2) to the JavaScript editor window

  • change code, and run the full function (select and Ctrl+Enter)

  • in addition, you could run individual statements of the function (basically line by line). For example, if you run var vol_grid = Globals.resourceUnitGrid("volume");, you’ll have also access to the Grid object, as vol_grid would the be a global variable. In that case, you can use the Javascript workspace window and inspect the properties of the grid (such as resolution, size, or name). Note, that in case of grids you can also just plot them (grid.paint(min_value, max_value))!

  • copy back working code to your JavaScript source file and run final tests with a full iLand simulation.

6.3.4 Declarative style and JavaScript objects

In JavaScript, a declarative style is an approach to programming where you focus on describing “what” you want to achieve rather than explicitly specifying “how” to achieve it. Declarative programming is often contrasted with imperative programming, where you explicitly outline step-by-step instructions for the computer to follow. Major key points of declarative programming are:

Descriptive code style
Declarative code is more descriptive and expresses the desired outcome in a clear and concise manner. Instead of detailing the steps to perform a task, you declare what you want to accomplish. For example, you describe a minimum DBH threshold for a thinning, but not the rules how to enforce that.

Level of abstraction
Declarative programming involves the use of abstractions that hide the low-level implementation details. This allows you to work at a higher level of abstraction, focusing on the overall structure and logic.

iLand uses such a declarative style mainly for ABE and BITE as it is a very flexible mechanism that allows mixing functionality provided by the model with user logic written in JavaScript (that may use again the iLand API to interact with the model). For instance, you define a management activity in ABE and describe certain properties of that activity:

  • When should the activity be executed?

  • Are there any conditions (e.g., minimum diameter) that need to be met?

  • What should happen, when the activity is run?

Particularly for the “what should happen” part it can be helpful to be able to control programmatically in a very detailed and descriptive way what exactly should happen. iLand heavily uses “declarative objects” for this task.

Declarative objects can contain different types of data, including simple values, arrays, and functions. JavaScript object literal syntax is a concise way to create such declarative objects. Objects in JavaScript are collections of key-value pairs, where each key is a string (or a Symbol) and each value can be of any data type, including other objects. The object literal syntax provides a clean and readable way to define objects directly in your code.

Here’s the basic structure of an object using object literal syntax:

const myObject = {
  key1: value1,
  key2: value2,
  // ...
};
  • myObject: The name of the variable holding the object.
  • key1, key2: The keys of the object, usually strings, but they can also be Symbols (introduced in ES6).
  • value1, value2: The corresponding values associated with the keys. Values can be of any data type, including numbers, strings, arrays, functions, and even other objects.

Example

const person = {
  name: "John Doe",
  age: 30,
  city: "Example City",
  hobbies: ["reading", "coding", "traveling"],
  greet: function() {
    console.log(`Hello, my name is ${this.name}!`);
  },
};

console.log(person.name);          // Output: John Doe
console.log(person.hobbies[1]);    // Output: coding
person.greet();                    // Output: Hello, my name is John Doe!