Friday, February 22, 2008

Say welcome to the Hydrologic Lady!

I want to signal the blog of the HydrologicLady.

On that site you will find a lot of useful tips and trick about how to use JGrass for environmental analyses, so if this is your field of interest, have a look at it.

She will also threat different interesting things to help, as for example this article about how to setup the GRASS environment inside JGrass and launch from within it GRASS commands.

Sunday, February 17, 2008

How to use the progressmonitor

This notes were in the udig developers manual, but they do not seem to exist any more (perhaps I just can't find them :)). However, before they get lost from the google cache (which is were I found them), I past them here for convenience:



* Always start the progress monitor and do at least 1 bit of work. For example:

monitor.beginTask("Working", 4);
monitor.worked(1);


* Always finish started job.

try{
monitor.beginTask("Working", 4);
monitor.worked(1);
// some work
}finally{
monitor.done();
}


* Make use of SubProgressMonitor if sending the monitor to another method:

try{
monitor.beginTask("Working", 8);
monitor.worked(1);

SubProgressMonitor sub=new SubProgressMonitor(monitor, 3);
doSomeWork(sub);
sub.done(); // don't forget to make sure the sub monitor is done

sub=new SubProgressMonitor(monitor, 3);
doSomeMoreWork(sub);
sub.done(); // don't forget to make sure the sub monitor is done (callee might not use it)

}finally{
monitor.done();
}

How to launch a tool programmatically

So you have your nice tool and want to launch it without having the user to click on a dedicated button?


IAction tool = ApplicationGIS.getToolManager().getToolAction(toolId, toolCategoryId);
tool.run();

Saturday, February 16, 2008

How to open a file with the OS standard application

SWT supplies a very nice class for this: org.eclipse.swt.program.Program
Just supply the absolute path to the file you want to open and the magic is done.



Program.launch(fullPath);

Friday, February 15, 2008

Docs: theJGrass workspace

Overview of a GRASS database structure:




Most important files and folders for now
  • WIND - contains the active processing region and the resolution
  • PROJ_INFO - contains the information about the projection
  • PROJ_UNITS - contains the information about projection units used
  • cell, fcell - contain the raster files
  • vector - contain the vector data since GRASS 6
  • sites_list - contain the sites type data, deprecated from GRASS 6 on, but maintained in JGrass

File structure of GRASS Location

A GRASS raster map consists of several files in several subdirectories in a mapset, organized as follows:

  • cellhd/: map header including projection code, coordinates representing the spatial extent of the raster map, number of rows and columns, resolution, and information about map compression;
  • cell/, fcell/ or grid3/: generic matrix of values in a compressed, portable format which depends on the raster data type (integer, floating point or 3D grid);
  • hist/: history file which contains metadata such as the data source, the command that was used to generate the raster map, or other information provided by the user;
  • cats/: optional category file which contains text or numeric labels assigned to the raster map categories;
  • colr/: optional color table;
  • cell_misc/: optional timestamp, range of values, quantization rules (for floating point maps) and null (no-data) files;

GRASS vector maps are stored in several separate files in a single directory. While the attributes are stored in either a DBF file, a SQLite file or in an external DBMS (PostgreSQL, MySQL, ODBC), the geometric data are saved as follows:

  • head: vector map ASCII header with information about the map creation (date and name), its scale and threshold;
  • coor: binary geometry file which includes the coordinates of graphic elements (primitives) that define the vector feature;
  • topo: binary topology file describes the spatial relationships between the map's graphic elements;
  • hist: history ASCII file with complete commands that were used to create the vector map, as well as the name and date/time of the map creation;
  • cidx: binary category index file which is used to link the vector object IDs to the attribute table rows;
  • dbln: ASCII file which contains definition(s) of link to attribute storage in database (DBMS).

Taken from the GRASS developer's manual

Docs: the active region

Since our migration towards the udig community I've been struggling around mostly with two concepts that didn't fit exactly into the picture of the existing udig:
  • the active region concept
  • the GRASS workspace
While the second is not yet solved in my mind, the active region concept has come to its (hopefully) final version.

Let's go through it:

1) what is the active region (AR)?

The AR is a defined portion of the territoy inside which calculations can be performed. It is defined by a bounday (n,s,w,e) and a particular resolution (in both northern and eastern directions) and a number of rows and columns, that are directly bound to the resolution (rows and columns can be only integer numbers, so the resolution has to recalculated if the boundary calculus with the resolution doesn't gain integer rows and cols).

The AR concept is particularly useful for example when you have very large maps that don't even fit in you pc's memory and you want to do calculations on a smaller portion of the map or on the whole map, but with lower resolution. In these cases the AR can be set to a particular portion of the map or the resolution can be lowered and the models and algorythms from that moment on will do calculations only on that region, as if the map was contained only inside of it.

2) How to use the AR?

The AR is visually displayed by the Active Region Graphic, which can be found in the catalog view with the other mapgraphic objects.


Once you drag that object onto the map, the first time you will be asked for the mapset you want to use the active region from (for informations about what a JGrass workspace-location-mapset-etc is, have a look here).




Once choosen one mapset, the active region magically appears.


From that moment on an AR definition occurred and every algorythm and module that needs it can retrieve it and use it. The user doesn't have to care, it is the developer's care to include this concept in the calculation models.

3) setting the AR

There are two ways to set the active region.

a) setting the AR by its properties panel

The AR properties panel is rather straight forward. You can set the mapset from which to take the AR from, but please pay attention about the fact that the AR is consistent with the maps you are working on, i.e. don't use maps from different mapsets or locations, since you could gain very fancy results :) Always the same thing: always try to know what you are doing.

You can set boundaries and resolutions. Resolutions are recalculated everytime to be in shape woth the rows-cols consistency.

You can also set some style proprties, like color and transparency.

And last but not least you can also set the region to a particular map, raster or vector. in the case of a raster map, its fileregion (i.e. its boundaries and resolution) is taken and the AR is set to that. In the case of a vector map the region is set to its boundary, but then, since vector maps do not have a resolution definiton, the region is adjusted to snap its boundaries and resolution to the prior AR. This is mandatory to be able to coninue to work on a consistent way on the raster grid.




b) setting the AR by the box tool

The second way to set the AR is the Active Region tool that can be found in the toolbar. At the moment of writing it has the same icon of the selection tool, I hope to find some time to change it soon.



With this tool you will be able to just draw a box and the active region will be set to that box. In fact the selected box is adapted to snap to the prior AR and naturally will have the resolution of the prior AR.


Monday, February 11, 2008

How to retrieve an image for an action or button

There are several way to achieve this:

1) many common images (undo, redo, file, folder, cut, copy) are available through the org.eclipse.ui.ISharedImages interface:

Image folderImg = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);


2) If you have your own image in the plugin path, with the help of AbstractUIPlugin:

ImageDescriptor imageDescriptorFromPlugin = AbstractUIPlugin.imageDescriptorFromPlugin(MyFunnyPlugin.PLUGIN_ID, "icons/stop.gif");

The good thing is that the path is relative to the plugin containing it (in this case MyFunnyPlugin) and is URL-alike, i.e. slash on every operating system.
Sometimes all you need is the descriptor, as in the case of actions:


Action stopGpsAction = new Action(){
public void run() {
// stop that thing
}
};
stopGpsAction.setImageDescriptor(imageDescriptorFromPlugin));


sometimes you need the image instead, as in the case of icons for buttons:


Button stop = new Button(commandGroup, SWT.PUSH | SWT.BORDER);
stop.setImage(imageDescriptorFromPlugin.createImage());



3) If however your resource lies in the same package of the calling class, you can exploit:

ImageData imageData = new ImageData(getClass().getResourceAsStream("legenda_pioggia.png"));
Image image = new Image(display, imageData);

Friday, February 8, 2008

How to get coordinates from the map

Since we are talking about querying a map, let's quickly see the important part of the code behind:

1) the MapMouseListener has to be implemented by your tool
2) the class implementing it has to be added as listener to the mapdisplay:


IMap map = ApplicationGIS.getActiveMap();
((ViewportPane) map.getRenderManager().getMapDisplay()).addMouseMotionListener(implementingClass);


3) your implementing class will have some nice method, one of which should look like:



public void mouseReleased( MapMouseEvent event ) {
IMap map = ApplicationGIS.getActiveMap();
if (map != null) {
// event.x, event.y gives the pixel position of the mouse in the map
// next ask for the world coordinates of the clicked point
Coordinate coord = map.getViewportModel().pixelToWorld(event.x, event.y);
// and here the pixel position of the mouse on the screen
Point mouse = getDisplay().getCursorLocation();

// do something with it :)
}
}

Docs: query raster maps in JGrass

The raster query command is a very useful tool. It can be activated by searching under:

Window -> Show View -> Other

Which wil then open something like the below and from which you will choose the raster query tool:



The tool will open a view as you can see it in the first picture on the lower part, inside of which you can choose a map to query. Click on the button, choose your map and if you move over the map, in the view the central point, the 8 pixels sourounding it and the relative world coordinates are shown.

Sometimes this however gives a feeling like watching a movie with subtitles, you are never able to concentrate on the movie. That is why you can also click on the map, which will open a nice Balloonwindow (thanks to http://www.novocode.com/swt/) with the needed info in it:



The best part of it?
Go into a text editor and paste... huuuuu... :)

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 :)