Create operations

In gvSIG 2.0 is defined the operation concept. This is a class which inherits from GeometryOperation that implements a specific operation and associate it to a concrete GeometryType.

The GeometryOperation class needs two methods to be implemented: in one hand will be getOperationInxex, which returns the internal operation code. Operations must have a name but, in runtime, gvSIG will establish a correspondency between the names and codes of the operations. Following the convention, all the operations must have a public and static variable of the int type which will be intialized with the getGeometryOperationCode method from the GeometryManager. This method fixes the operation code value and it would be consulted to execute a concrete operation in a third class.

In the other hand, besides getOperationInxex, an operation will have a method called invoke which will be invoke when the operation is executed. The parameters are the geometry, in which we are going to do the operation, and the operation context (commented in the next sections).

In the next example we will create an operation called println which prints in the console the geometry that executes the operation:

public class PrintLn extends GeometryOperation {
  public static final String NAME = "println";
  public static final int CODE = GeometryLocator.getGeometryManager().getGeometryOperationCode(NAME);

  public int getOperationIndex() {
    return CODE;
  }

  public Object invoke(Geometry geom, GeometryOperationContext ctx) throws GeometryOperationException {
    System.out.println(geom.toString());
    return null;
  }
}

When we will have created an operation, we must register it in the application. The next section shows how to do it.

Register operations

If we want to use an operation, it must be registered previously. If we try to execute an operation and it isn't registered by the GeometryManager, we will get an exception.

The operation registration must be done in a class extended from the GeometryLibrary, in the postinitialize method. The registerGeometryOperation method takes care of the registration of an operation. The operations must be registered indicating the concrete geometry type (or several geometry types) which are associated with them. For that, can be operations that only are valid in a subset of geometries.

For example, the PrintLn operation commented in the previous section can be executed by all geometry types. To do it, is necessary to register it following this steps:

GeometryManager geometryManager = GeometryLocator.getGeometryManager();
geometryManager.registerOperation("PrintLn", printLn);

The registerOperation method accepts two parameters: the first one is the name of the operation and the second one is the object which implements the operation. In this case, we are registering the draw operation in the stardard output and it's associated to all geometries.

There's a mechanism to register operations associated with a concrete geometry types. We can create, for example, an operation PrintLn to write only 2-dimensional points. The operation will be the next:

public class printLnPoint2D extends GeometryOperation {
  public static final String NAME = "println";
  public static final int CODE = GeometryLocator.getGeometryManager().getGeometryOperationCode(NAME);

  public int getOperationIndex() {
    return CODE;
  }

  public Object invoke(Geometry geom, GeometryOperationContext ctx) throws GeometryOperationException {
    Point point = (Point)geom;
    System.out.println("Point 2D, X=" + point.getX() + " Y=" + point.getY());         
    return null;
  }
}

This operation only works with objects of the type 2-dimensional point. For that, in the method registerOperation we must specify two new parameters: the type and subtype of the geometry which can use the operation. When the PrintLn operation is invoked, the code of the operation PrintLn will be executed for all the geometry types except to the 2-dimensional Point, which will execute the specific operation.

GeometryManager geometryManager = GeometryLocator.getGeometryManager();
geometryManager.registerOperation("PrintLn", printLnPoint2D, Geometry.TYPES.Point, Geometry.SUBTYPES.GEOM2D);

The registerOperation method is overwritted to let us to associate an operation to all the geometry of a concrete type (for all the subtypes). This example shows how to do an operation that prints points, independently of their dimensions.

public class printLnPoint extends GeometryOperation {
  public static final String NAME = "println";
  public static final int CODE = GeometryLocator.getGeometryManager().getGeometryOperationCode(NAME);

  public int getOperationIndex() {
    return CODE;
  }

  public Object invoke(Geometry geom, GeometryOperationContext ctx) throws GeometryOperationException {
    Point point = (Point)geom;
    System.out.println("Point, ");
    for (int i=0 ; i<point.getDimension() ; i++){
      System.out.println("Coordinate i =" + point.getCoordinateAt(i));
    } 
    return null;
  }
}

To register this operation, we will execute this steps:

GeometryManager geometryManager = GeometryLocator.getGeometryManager();
geometryManager.registerOperation("PrintLn", printLnPoint, Geometry.TYPES.Point);

The last option is to register an operation for all the geometries of the same subtype. A clearly example of this operation is the 2-dimensional draw, which can be executed for all the 2-dimensional geometries. To register this operation we must execute:

geometryManager.registerOperationBySubtype("Draw2D", draw2D, Geometry.SUBTYPES.GEOM2D);

Operation invocation

We only can invoke an operation if it had been registered previously. When it's registered, we can execute the operation in a geometry only with the operation's name. For example, to execute the PrintLn operation we will execute the next code (is assumed that exists a 2-dimensional point called point registered and the operation was registered for this geometry type).

Object obj = point.invokeOperation("println", null);

The operations will return an object of the Object type with the result of the operation. In the invocation of the invokeOperation method appears a second parameter that is not used in the example. This argument is of the GeometryOperationContext type and it's a Map which contains all the input parameters for the operations.

In the next example we invoke an operation called intersects that is executed over two geometries. For that, is assumed that exists two Surface's in 2 dimensions called surface1 and surface2, previously created:

GeometryOperationContext geomertyOperationContext = new GeometryOperationContext();
geometryOperationContext.setAttribute("geom2", surface2);
Boolean results = (Boolean)surface1.invokeOperation("intersects", geomertyOperationContext);

To make this code run, is supposed that the intersects operation is executed in a "context" where exists a second geometry which is registered with the name "geom2". This, increase the difficulty to use it, because it will necessary to know all the names of the objects that are used to create the correct context.

To avoid this, you can create a specific context classes to execute a concrete operation. In the intersects case, we can create a class like this:

public class IntersectsGeometryOperationContext extends
              GeometryOperationContext {
 
  public IntersectsGeometryOperationContext(Geometry geom){
    this.setGeom(geom);
  }
      
  public Geometry getGeom() {
    return (Geometry)getAttribute("geom2");
  }

  public void setGeom(Geometry geom) {
    setAttribute("geom2", geom);
  }
}

Using this steps it isn't necessary to know the Map's name where the second geometry is stored. In the next example is going to execute the same code but this time is going to use the specific context for the operation:

IntersectsGeometryOperationContext intersectsContext = new GeometryOperationContext(surface2);
Boolean results = (Boolean)surface1.invokeOperation("intersects", intersectsContext);

It isn't necessary to use the Map provided by the GeometryOperationContext. For that, you can create class attributes to store the context parameters and make a personal implementation.

Another possibility to execute operations is using their code. The operations have by agreement a static variable of the integer type which contains the operation code. To execute the associated code to the intersecs operation we will run the following code (is assumed that exists a IntersectsOperation class):

IntersectsGeometryOperationContext intersectsContext = new GeometryOperationContext(surface2);
Boolean results = (Boolean)surface1.invokeOperation(Intersects.CODE, intersectsContext);

All this commented examples are used to execute an operation for a concrete geometry type. But it's interesting to bear in mind that we are passing the operation identificator by parameter to the geometry which will look for the operation to execute. In some cases, where there's a loop where are executed the same operation several times, the execution using this mechanism could be slow. For that, exists a way to do the operation access more efficiently.

The GeometryManager has a method called getGeometryOperation which returns an operation for a concrete geometry type and subtype. If, for example, we want to execute the intersects operation in a loop of 2-dimenasional Surface's (is assumed that we have all the surfaces in a variable of the type List<Surface> called surfaces) over a known surface (is also supposed that exists a variable of the type surface2) we can execute the next code:

GeometryOperation intersectsOperation = geometryManager.getGeometryOperation(Intersects.CODE, Geometry.TYPES.Point, Geometry.SUBTYPES.GEOM2D);
IntersectsGeometryOperationContext intersectsContext = new GeometryOperationContext(surface2);
for (int i=0 ; i<surfaces.size() ; i++){
  intersectsOperation.invoke(surfaces.get(i), intersectsContext);
}