Friday, February 8, 2008

Docs: How to understand JGrass's console engine - part IV, how to use it without JGrass?

I really want to drop a note for developers interested in this.

Alright, The JGrass console engine can be used without JGrass. All you need are the two console plugins from the JGrass package:

  • eu.hydrologis.jgrass.console
  • eu.hydrologis.jgrass.console.editor

At that point lets code:



/*
* prepare to use the console environment
*/
PipedOutputStream out = new PipedOutputStream();
PrintStream pStream = new PrintStream(out);
JGrass console = ConsolePlugin.console();
if (null == console) {
// throw something
}


Set some options to the engine:



ProjectOptions projectOptions = new ProjectOptions(pStream, pStream, pStream);
PreferencesInitializer.initializeStartupProjectOptions(projectOptions);
projectOptions.setOption(ProjectOptions.COMMON_GRASS_DATABASE, new File(
locationPath).getParent());
projectOptions.setOption(ProjectOptions.COMMON_GRASS_LOCATION, new File(
locationPath).getName());
projectOptions.setOption(ProjectOptions.COMMON_GRASS_MAPSET, mapsetName);
projectOptions.setOption(ProjectOptions.CONSOLE_COMPILE_ONLY, new Boolean(false));
projectOptions.setOption(ProjectOptions.CONSOLE_ASYNC_MODE, new Boolean(false));
URL rtUrl = Platform.getBundle(ConsolePlugin.PLUGIN_ID).getResource("rt"); //$NON-NLS-1$
String rtPath = null;
try {
rtPath = FileLocator.toFileURL(rtUrl).getPath();
} catch (IOException e) {
HydrocarePlugin.log("HydrocarePlugin problem", e); //$NON-NLS-1$
e.printStackTrace();
return;
}
projectOptions.setOption(ProjectOptions.CONSOLE_DIRECTORY_INCLUDE,
new String[]{rtPath});


Note that the FileLocator.toFileURL stuff is needed only if inside an RCP application.

Now create the command you want to execute:


String command = "g.region -p";


And execute it!



console.dispatch(projectOptions, command);





UPDATE
_________


I just created a class to use the console in standalone mode:



/**
* Standalone console engine
*
* @author Andrea Antonello - www.hydrologis.com
*/
public class JGrassConsole {
private final String grassDbPath;

private final String locationName;

private final String mapsetName;

private final String rtPath;

private boolean doDebug = false;

private String gisbase = "/"; //$NON-NLS-1$

public JGrassConsole(String grassDbPath, String locationName,
String mapsetName, String rtPath, String cmd, boolean debug,
String grass) {
this.grassDbPath = grassDbPath;
this.locationName = locationName;
this.mapsetName = mapsetName;
this.rtPath = rtPath;
this.doDebug = debug;
if (grass != null) {
gisbase = grass;
}
/*
* prepare to use the console environment
*/
PrintStream outStream = new PrintStream(System.out);
PrintStream errStream = new PrintStream(System.err);
JGrass console = (JGrass) new ConsoleEngine();
if (null == console) {
// throw something
}

ProjectOptions projectOptions = new ProjectOptions(outStream,
outStream, errStream);

initialize(projectOptions);

console.dispatch(projectOptions, cmd);
}

/**
* @param args
*/
@SuppressWarnings("nls")
public static void main(String[] args) {

String grassDbPath = null;
String locationName = null;
String mapsetName = null;
String rtPath = null;
String cmd = null;
String grass = null;
boolean debug = false;

for (String arg : args) {
System.out.println(arg);
if (arg.startsWith("-db")) {
grassDbPath = arg.split("=")[1];
}
if (arg.startsWith("-loc")) {
locationName = arg.split("=")[1];
}
if (arg.startsWith("-maps")) {
mapsetName = arg.split("=")[1];
}
if (arg.startsWith("-rt")) {
rtPath = arg.split("=")[1];
}
if (arg.startsWith("-cmd")) {
cmd = arg.split("=")[1];
}
if (arg.startsWith("-deb")) {
debug = true;
}
if (arg.startsWith("-grass")) {
grass = arg.split("=")[1];
}
}

if (grassDbPath == null || locationName == null || mapsetName == null
|| rtPath == null || cmd == null) {
System.out
.println("USAGE: -db= -loc= -maps= -rt= -cmd= [-grass=] [-debug]");
System.exit(1);
}

new JGrassConsole(grassDbPath, locationName, mapsetName, rtPath, cmd,
debug, grass);

}

public void initialize(ProjectOptions options) {
options.setOption(ProjectOptions.CONSOLE_ASYNC_MODE, false);
options.setOption(ProjectOptions.CONSOLE_THREAD_RESTRICTION, 0);
options.setOption(ProjectOptions.CONSOLE_COMPILE_ONLY, false);

// Logging Level: DEBUG
options.setOption(ProjectOptions.CONSOLE_LOGGING_LEVEL_DEBUG, doDebug);
// Logging Level: TRACE
options.setOption(ProjectOptions.CONSOLE_LOGGING_LEVEL_TRACE, doDebug);
// Include directory; RTTI - runtime type informations - the
// reserved
// words...
options.setOption(ProjectOptions.CONSOLE_DIRECTORY_INCLUDE,
new String[] { rtPath });

// Source directory; default script file location...
options.setOption(ProjectOptions.CONSOLE_DIRECTORY_SOURCE, null);

// User information - the home directory of the current user...
options.setOption(ProjectOptions.NATIVE_MODEL_USER_HOME, System
.getProperty("user.home")); //$NON-NLS-1$

// User information - the user name of the current user...
options.setOption(ProjectOptions.NATIVE_MODEL_USER_NAME, System
.getProperty("user.name")); //$NON-NLS-1$

// Debug mode...
options.setOption(ProjectOptions.NATIVE_MODEL_DEBUG, doDebug);

// Installation folder of GRASS...
options.setOption(ProjectOptions.NATIVE_MODEL_GISBASE, gisbase);

// GRASS database, location, mapset path...
options.setOption(ProjectOptions.COMMON_GRASS_MAPSET, grassDbPath
+ File.separator + locationName + File.separator + mapsetName
+ File.separator);

options.setOption(ProjectOptions.JAVA_MODEL_TIME_DELTA, null);
options.setOption(ProjectOptions.JAVA_MODEL_TIME_ENDING_UP, null);
options.setOption(ProjectOptions.JAVA_MODEL_TIME_START_UP, null);
}

}

Docs: How to understand JGrass's console engine - part III, how to use it?

1) Java scripting through beanshell

The easiest start is always playing a bit around. open the console and write some simplified java code, as reported in the image below.


To execute the script right-click and choose run from the menu. The result of the script will be seen in the output window.

2) grass commands execution

Commands wrapped by


grass {

}

are interpreted as native grass commands and executed like that. Just use the GRASS syntax.

See below the example of the execution of the help of the g.region command.


3) the JGrass modeling language based on OpenMI

Same as for GRASS commands, JGrass commands are wrapped inside


jgrass {

}


The JGrass modeling language will need a larger article, so I don't even dare to talk about it now in this short space.

However, have a look at the shot below, showing a mixed script between GRASS, JGrass and beanshell, passing variables from one language to the other, gaining return values from the JGrass models. This all gives the possibility to easily create also complex algorythm without having to know the development framework... no compilation... just write and execute!


Docs: How to understand JGrass's console engine - part II, where does the console live?

To open the console click on the console icon in the toolbar at the top.


An editor winod should appear and give you some advices. By rightclicking in the window you get the menu with the needed execution commands.

In oder to properly work there are some settings that have to be controlled. Just go in the preferences and search for the Console tab. If everything was installed properly, you will only need to set the path to fit to your GRASS installation, which is needed if you want to execute GRASS commands:



Here you will have to set GISBASE, the binary path and the libraries path, with additional libraries path addition for MACOSX systems. That done, you are ready to rumble.

The global GRASS database setting, which you found playing around in the tabs seen above can be overridden for every console session. This is done in the Runtime Preferences which pop up from the menu appeared by right-clicking.



The console mostly works with OpenMI based models and therefore has support for chaining models throught a timeline.

Docs: How to understand JGrass's console engine - part I, a boring introduction

I was asked many times about the link between JGrass and GRASS. This could be one answer finally.
In the past JGrass was trying to wrap GRASS in the sense of user interface, i.e. GUI, and that was all. Dark periods of JNI and heavy maintainance horrors signed those times and the chosen java platform portability was often gone with the wind.

So we decided to make JGrass an I-can-live-also-alone entity. The material was there, since JGrass is heavily sponsored with knowledge by Riccardo Rigon's research team at the University of Trento, faculty of engineering.

So we ported to pure java some small I/O parts in order to be able to read GRASS's workspace and raster and at that time vector data (new vector is not there yet). That was a good choice, since it made us independent from the operation system and etc. etc. etc.

Also in the new JGrass, as of what we are trying to do with the marriage with udig, the analysis engine of JGrass has been extracted and isolated to what we called the console engine. This engine is able to add calculation power to JGrass, as well as beanshell scripting and scripting in the "JGrass modelling language". The important thing of this engine is that it is also able to live in standalone modus and also that it can launch GRASS native commands. So yes, that is the interaction between JGrass and GRASS. JGrass works with the exactly same environment and puts efforts to stay compatible with GRASS, and it can execute GRASS commands mixed up with scripting and JGrass commands.

How to do that?
Alright, the steps are probably more than one:
1) where does the console live?
2) how to use it?
3) how to use it without JGrass?

Thursday, February 7, 2008

How to properly log errors, so that a user can send them to you

One of the biggest problems we had in the past in JGrass, was the fact that the users usually send an email to the list telling that JGrass "doesn't work". No other explenation, no way for us to understand. Since JGrass at that time was really buggy, it all got a mantainance nightmare.

The new JGrass has a solution to all this given by the Eclipse/Udig framework, which is the possibility to log the errors and let the user send the log to some poor guy that then has to understand what is happening. This is much better for us than the "doesn't work".

Obviously a developer will have to include some small snippet into his code to get the logging enabled. Here it is:

Step 1: insert the following code snippet into your Plugin Activator class



public static void log( String message2, Throwable t ) {
if (getDefault() == null) {
t.printStackTrace();
return;
}
String message = message2;
if (message == null)
message = ""; //$NON-NLS-1$
int status = t instanceof Exception || message != null ? IStatus.ERROR : IStatus.WARNING;
getDefault().getLog().log(new Status(status, PLUGIN_ID, IStatus.OK, message, t));
}


Step 2: call the logging method from every needed part of your code, i.e. everywhere an exception is thrown and you would like to know it.
For example in the JGrass catalog plugin that would look like:



try {
// ... your code
} catch (Exception e) {
JGrassPlugin.log("JGrassPlugin problem:eu.hydrologis.udig.catalog.internal.jgrass
#JGrassMapGeoResource#getIdentifier", e);
e.printStackTrace();
}


this would be an exception thrown inside the
class: JGrassMapGeoResource
package: eu.hydrologis.udig.catalog.internal.jgrass
method: getIdentifier

You do not think I wrote that by myself, right?
The Eclipse template engine helps you in this. If you want it the same as above, just add the following template to the java->editor->templates list (all in one line):


${pluginActivator}.log("${pluginActivator} problem:
${enclosing_package}#${enclosing_type}#
${enclosing_method}", ${throwable}); //$$NON-NLS-1$$



This will automagically insert the method-class-package, you just have to supply the plugin activator class name and the name of the exception.


If everything is done well then the user should be able to go under submit the log


And send us the content of everything that has been logged.



Please use that thing :)

Wednesday, February 6, 2008

How to easily add menus & actions to jface/swt viewers - adding a table row

Assume you created a nice new empty table to be filled by a user.
And let's say you created a first empty row with two columns.


List elements = new ArrayList();

TableViewer tViewer = new TableViewer(parent, SWT.MULTI);
table = tViewer.getTable();
table.setLayoutData(new GridData(GridData.FILL_BOTH));
table.setHeaderVisible(true);
table.setLinesVisible(true);
TableLayout layout = new TableLayout();
layout.addColumnData(new ColumnWeightData(50, true));
layout.addColumnData(new ColumnWeightData(50, true));
table.setLayout(layout);
TableColumn dateColumn = new TableColumn(table, SWT.LEFT);
dateColumn.setText("DATE");
TableColumn dischargeColumn = new TableColumn(table, SWT.LEFT);
dischargeColumn.setText("DISCHARGE");
TableLabelProvider sLP = new TableLabelProvider(); // implement this yourself
tViewer.setLabelProvider(sLP);
tViewer.setContentProvider(new ArrayContentProvider());
// the empty row
elements.add(new String[]{"", ""});
// set the input
tViewer.setInput(elements);


what to do very quickly to add a new row?
After fiddling a bit around with listeners and such stuff, I decided to exploit the menus:


// add a popup for new rows
MenuManager popManager = new MenuManager();
IAction menuAction = new NewRowAction();
popManager.add(menuAction);
Menu menu = popManager.createContextMenu(table);
table.setMenu(menu);


And how easy is the creation of the action?
Like that:


private class NewRowAction extends Action {
public NewRowAction() {
super("Add a new row");
}
public void run() {
elements.add(new String[]{"", ""});
tViewer .refresh();
}
}


et voila', right-click on the table and tell it to add the new row.

Tuesday, February 5, 2008

Docs: just more of the legend

This was sent to me and I had to show it, I love this map :)

You can see JGrass showing a raster map containing the geological types in our region. Isn't that category based legend slick? :)



Thanks to Silli for the screenshot :)