AMAPstudio

User Tools


How to write an intervention tool

An intervention tool (intervener) applies a process on a scene and returns a modified scene. The new scene is connected to a new step linked after the original step in the project.

In forestry, the usual intervention is thinning: “cut trees in the scene”. However, it is possible to implement all kinds of interventions, e.g. replanting, fertilization, effect of the wind…

A simple example: the Training Thinner

This intervener cuts the trees with a diameter lower than a given value with a given probability.

Reminder: an entry must be added in the etc/extension.list of Capsis otherwise the extension will not be found by the ExtensionManager. To do it, you can type, at the root of the capsis installation directory : sh capsis.sh -se (under Linux or MacOS) or capsis -se (under Windows) to update the list of interveners, or edit directly the etc/extension.list file :

...
training.extension.intervener.TraThinner=enabled
training.extension.intervener.TraThinner%subType=SelectiveThinner
...

Step by step detailled comments

The code of this very simple thinner is presented in commented blocks below. The complete code can be found at the bottom of this document in case you would like to copy it to make a new intervener.

package training.extension.intervener;
 
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
 
import jeeb.lib.util.Log;
import jeeb.lib.util.Translator;
import jeeb.lib.util.annotation.Ignore;
import jeeb.lib.util.autoui.SimpleAutoDialog;
import jeeb.lib.util.autoui.annotations.AutoUI;
import jeeb.lib.util.autoui.annotations.Editor;
import training.model.TraModel;
import capsis.defaulttype.Tree;
import capsis.defaulttype.TreeList;
import capsis.kernel.GModel;
import capsis.kernel.GScene;
import capsis.kernel.Step;
import capsis.kernel.automation.Automatable;
import capsis.kernel.extensiontype.Intervener;
import capsis.util.Group;
import capsis.util.GroupableIntervener;

This intervener is part of the training module. This module can not be found in the svn repository: it is built by the trainees during their initial Capsis training. It is based on the template.extension.intervener.TplThinner.

/** TraThinner is an example of thinning tool for Training. 
 * 
 *  @author F. de Coligny - December 2010
 */
@AutoUI(title="TrainingThinner", translation="TraThinner")
public class TraThinner implements Intervener, GroupableIntervener, Automatable {
 
	static {
		Translator.addBundle ("training.extension.intervener.TraThinner");
	}
 
	public static final String NAME = "TraThinner";
	public static final String VERSION = "1.0";
	public static final String AUTHOR =  "F. de Coligny";
	public static final String DESCRIPTION = "TraThinner.description";
	static public String SUBTYPE = "SelectiveThinner";
 
	@Ignore
	private boolean constructionCompleted = false;  // if cancel in interactive mode, false
	@Ignore
	private GScene scene;  // reference scene: will be altered by apply ()
	@Ignore
	private GModel model;
	@Ignore
	protected Collection<Tree> concernedTrees;
 
	@Editor
	protected double selectionDbh = 20;  // cm, cut no trees above this dbh
	@Editor
	protected double selectionProba = 0.5;  // [0, 1], cut the trees with this proba only
 
	@Ignore
	private Random random;
 

The thinner implements Intervener: needed for Intervener extensions, Automatable: it can be used in Capsis automations (not covered here, @Ignore is related to the automations) and GroupableIntervener: it can be applied on a restricted part of the scene.

Note: this thinner is compatible only with the Training model, an individual based model. That is why we talk about trees hereafter. Another intervener could be compatible with another kind of model, e.g. without trees (see the matchWith () method below).

The interactive mode is based on the AutoUIs: a GUI panel will be automatically created to get the values for the variables tagged with the @Editor annotation.

The Translator is told to load a translation bundle for this class (the language was chosen by the user at Capsis launch time).

The constants NAME, VERSION… are needed for the extensions management. NAME, DESCRIPTION and SUBTYPE will be translated and it is a good idea to translate 'TraThinner', 'TraThinner.description' and 'SelectiveThinner' in the labels files: TplThinner_en.properties and TplThinner_fr.properties in the same directory.

The scene field is a reference to the scene that will be altered by this intervener (in apply ()), model is a reference to the GModel instance connected to the current Project.

The two fields selectionDbh and selectionProba are the main properties of the intervener: the apply () method will run the process depending on the values of these two variables. They are tagged with the @Editor annotation to be added to the automatic GUI panel when needed (see below initGUI ()).

Note: both variables are provided with default values.

The Random object is a reference to a random number generator, it will be used in apply ().

	/** Default constructor.
	 */
	public TraThinner () {}
 
 
	/** Script constructor.
	 */
	public TraThinner (double selectionDbh, double selectionProba) {
 
		this.selectionDbh = selectionDbh;
		this.selectionProba = selectionProba;
 
		constructionCompleted = true;
 
	}
 
 
	/** Init the thinner on a given scene.
	 */
	@Override
	public void init(GModel model, Step s, GScene scene, Collection c) {
		this.scene = scene;	// this is referentScene.getInterventionBase ();
		this.model = model;
		this.random = new Random ();
 
		if (c == null) {
			concernedTrees = (Collection<Tree>) ((TreeList) scene).getTrees ();  // all trees
		} else {
			concernedTrees = c;  // restrict to the given collection
		}
 
		constructionCompleted = true;
	}

The script constructor makes it possible to create and initialise the intervener in a Capsis script in Java.

The init () method retrieves the scene to be changed, the step related to this scene, the matching GModel instance and the trees concerned by the intervener. If the c collection is null, all trees in the scene are concerned by the process, else the process should restrict to this collection of trees.

	/** Open a dialog to tune the thinner.	
	 */
	@Override
	public boolean initGUI() throws Exception {
		// Interactive start
		SimpleAutoDialog dlg = new SimpleAutoDialog (this, false);  // false: no extra buttons
		dlg.setVisible(true);
 
		constructionCompleted = false;
		if (dlg.isValidDialog ()) {
			// Valid -> ok was hit and all checks were ok
			constructionCompleted = true;
		}
		dlg.dispose ();
		return constructionCompleted;
 
	}

The initGUI () method can be used to tune the intervener parameters in interactive mode. It opens a dialog box to get values for the two parameters: selectionDbh and selectionProba.

The initGUI () method of this particular intervener relies on the AutoUIs and only opens a SimpleAutoDialog to show the intervener object itself. This will create a dialog with a panel containing entry widgets for the instance variables tagged with @Editor. Checks will be performed on 'Ok' to verify that the typed values match the types of the fields. More checks could be added, see the AutoUIs for more details.

If the Dialog is canceled, the constructionCompleted boolean stays false to prevent running the intervener.

How to create a custom dialog ?

It is also possible to build a more specific dialog box with specific features as explained here.
	/** Extension dynamic compatibility mechanism.
	 *  This matchwith method checks if the extension can deal (i.e. is compatible) with the referent.
	 */
	static public boolean matchWith (Object referent) {
		try {
			if (!(referent instanceof TraModel)) {return false;}
 
		} catch (Exception e) {
			Log.println (Log.ERROR, "TraThinner.matchWith ()", 
					"Error in matchWith () (returned false)", e);
			return false;
		}
 
		return true;
	}

The matchWith() method can be found in all extensions. It performs tests on the given 'referent object' to check that it can work with it. For interveners, the Object is a GModel instance. This intervener is only compatible with the Training model (test on the TraModel class).

	/** GroupableIntervener interface. This intervener acts on trees,
	 *  tree groups can be processed.
	 */
	public String getGrouperType () {return Group.TREE;}
 
 
	/** These checks are done at the beginning of apply ().
	 *  They are run in interactive AND script mode.
	 */
	public boolean isReadyToApply () {
 
		if (!constructionCompleted) {
			// If cancel on dialog in interactive mode -> constructionCompleted = false
			Log.println (Log.ERROR, "TraThinner.isReadyToApply ()",
					"constructionCompleted is false. TraThinner is not appliable.");
			return false;
		}
 
		if (selectionDbh <= 0) {
			Log.println (Log.ERROR, "TraThinner.isReadyToApply ()",
					"Wrong selectionDbh: "+selectionDbh+". TraThinner is not appliable.");
			return false;
		}
 
		if (selectionProba < 0 || selectionProba > 1) {
			Log.println (Log.ERROR, "TraThinner.isReadyToApply ()",
					"Wrong selectionProba: "+selectionProba+". TraThinner is not appliable.");
			return false;
		}
 
		return true;
	}

The intervener is a GroupableIntervener, it declares it can deal with groups of trees : see the getGrouperType() method above.

The isReadyToApply() method checks that the parameters values are ok. It is called at the beginning of apply() and thus is run in both script and GUI modes → do not open message dialogs here!

You may test that the requested values are given, that their type and values are correct. If trouble, it is a good idea to write in the Log and to return false.

	/** Makes the action: thinning.
	 */
	public Object apply () throws Exception {
		// Check if apply is possible
		if (!isReadyToApply ()) {
			throw new Exception ("TraThinner.apply () - Wrong input parameters, see Log");
		}
 
		scene.setInterventionResult (true);
 
		// iterate and cut
		for (Iterator i = concernedTrees.iterator (); i.hasNext ();) {
			Tree t = (Tree) i.next ();
 
			// do we cut this tree ?
			if (t.getDbh () <= selectionDbh) {  // yes
 
				double p = random.nextDouble ();
 
				if (p < selectionProba) {
 
					// remove the tree
					i.remove ();
					((TreeList) scene).removeTree (t);
					// remember this tree has been cut
					((TreeList) scene).storeStatus (t, "cut");
 
				}
 
			}
 
		}
 
		return scene;
	}
 
 
	/** Intervener
	 */
	@Override
	public void activate() {
		// not used
 
	}
 
}

The apply() method is the main method in the intervener: it implements the job.

First, it checks the isReadyToApply() method and in case it returns false, it throws an exception.

Then it says that the scene is the result of an intervention.

Finally, it iterates on the concerned trees and decides which ones are removed from the scene.

The activate() method is unused here.

The complete source code of the intervener

package training.extension.intervener;
 
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
 
import jeeb.lib.util.Log;
import jeeb.lib.util.Translator;
import jeeb.lib.util.annotation.Ignore;
import jeeb.lib.util.autoui.SimpleAutoDialog;
import jeeb.lib.util.autoui.annotations.AutoUI;
import jeeb.lib.util.autoui.annotations.Editor;
import training.model.TraModel;
import capsis.defaulttype.Tree;
import capsis.defaulttype.TreeList;
import capsis.kernel.GModel;
import capsis.kernel.GScene;
import capsis.kernel.Step;
import capsis.kernel.automation.Automatable;
import capsis.kernel.extensiontype.Intervener;
import capsis.util.Group;
import capsis.util.GroupableIntervener;
 
 
/** TraThinner is an example of thinning tool for Training. 
 * 
 *  @author F. de Coligny - December 2010
 */
@AutoUI(title="TrainingThinner", translation="TraThinner")
public class TraThinner implements Intervener, GroupableIntervener, Automatable {
 
	static {
		Translator.addBundle ("training.extension.intervener.TraThinner");
	}
 
	public static final String NAME = "TraThinner";
	public static final String VERSION = "1.0";
	public static final String AUTHOR =  "F. de Coligny";
	public static final String DESCRIPTION = "TraThinner.description";
	static public String SUBTYPE = "SelectiveThinner";
 
	@Ignore
	private boolean constructionCompleted = false;  // if cancel in interactive mode, false
	@Ignore
	private GScene scene;  // reference scene: will be altered by apply ()
	@Ignore
	private GModel model;
	@Ignore
	protected Collection<Tree> concernedTrees;
 
	@Editor
	protected double selectionDbh = 20;  // cm, cut no trees above this dbh
	@Editor
	protected double selectionProba = 0.5;  // [0, 1], cut the trees with this proba only
 
	@Ignore
	private Random random;
 
 
 
 
	/** Default constructor.
	 */
	public TraThinner () {}
 
 
	/** Script constructor.
	 */
	public TraThinner (double selectionDbh, double selectionProba) {
 
		this.selectionDbh = selectionDbh;
		this.selectionProba = selectionProba;
 
		constructionCompleted = true;
 
	}
 
 
	/** Init the thinner on a given scene.
	 */
	@Override
	public void init(GModel model, Step s, GScene scene, Collection c) {
		this.scene = scene;	// this is referentScene.getInterventionBase ();
		this.model = model;
		this.random = ,span(c,ass="kw1">new Random ();
 
		if (c == null) {
			concernedTrees = (Collection<Tree>) ((TreeList) scene).getTrees ();  // all trees
		} else {
			concernedTrees = c;  // restrict to the given collection
		}
 
		constructionCompleted = true;
	}
 
 
	/** Open a dialog to tune the thinner.	
	 */
	@Override
	public boolean initGUI() throws Exception {
		// Interactive start
		SimpleAutoDialog dlg = new SimpleAutoDialog (this, false);  // false: no extra buttons
		dlg.setVisible(true);
 
		constructionCompleted = false;
		if (dlg.isValidDialog ()) {
			// Valid -> ok was hit and all checks were ok
			constructionCompleted = true;
		}
		dlg.dispose ();
		return constructionCompleted;
 
	}
 
 
	/** Extension dynamic compatibility mechanism.
	 *  This matchwith method checks if the extension can deal (i.e. is compatible) with the referent.
	 */
	static public boolean matchWith (Object referent) {
		try {
			if (!(referent instanceof TraModel)) {return false;}
 
		} catch (Exception e) {
			Log.println (Log.ERROR, "TraThinner.matchWith ()", 
					"Error in matchWith () (returned false)", e);
			return false;
		}
 
		return true;
	}
 
 
	/** GroupableIntervener interface. This intervener acts on trees,
	 *  tree groups can be processed.
	 */
	public String getGrouperType () {return Group.TREE;}
 
 
	/** These checks are done at the beginning of apply ().
	 *  They are run in interactive AND script mode.
	 */
	public boolean isReadyToApply () {
 
		if (!constructionCompleted) {
			// If cancel on dialog in interactive mode -> constructionCompleted = false
			Log.println (Log.ERROR, "TraThinner.isReadyToApply ()",
					"constructionCompleted is false. TraThinner is not appliable.");
			return false;
		}
 
		if (selectionDbh <= 0) {
			Log.println (Log.ERROR, "TraThinner.isReadyToApply ()",
					"Wrong selectionDbh: "+selectionDbh+". TraThinner is not appliable.");
			return false;
		}
 
		if (selectionProba < 0 || selectionProba > 1) {
			Log.println (Log.ERROR, "TraThinner.isReadyToApply ()",
					"Wrong selectionProba: "+selectionProba+". TraThinner is not appliable.");
			return false;
		}
 
		return true;
	}
 
 
	/** Makes the action: thinning.
	 */
	public Object apply () throws Exception {
		// Check if apply is possible
		if (!isReadyToApply ()) {
			throw new Exception ("TraThinner.apply () - Wrong input parameters, see Log");
		}
 
		scene.setInterventionResult (true);
 
		// iterate and cut
		for (Iterator i = concernedTrees.iterator (); i.hasNext ();) {
			Tree t = (Tree) i.next ();
 
			// do we cut this tree ?
			if (t.getDbh () <= selectionDbh) {  // yes
 
				double p = random.nextDouble ();
 
				if (p < selectionProba) {
 
					// remove the tree
					i.remove ();
					((TreeList) scene).removeTree (t);
					// remember this tree has been cut
					((TreeList) scene).storeStatus (t, "cut");
 
				}
 
			}
 
		}
 
		return scene;
	}
 
 
	/** Intervener
	 */
	@Override
	public void activate() {
		// not used
 
	}
 
}
documentation/howtowriteanintervener.txt · Last modified: 2016/12/16 13:11 by beudez