Added by jgarnett, last edited by jgarnett on Jan 17, 2008  (view change)

Labels

 
(None)

There is no "X" axis. Seriously. We are supposed to use the CoordinateReferenceSystem to figure out what the data is measuring.

Why this is Not a Problem

In the normal course of events you will set up a series of math transforms to map from your data representation to the screen.

From Data to World to Screen

We want to set up the following:

  • Create a "screen" CoordinateReferenceSystem using DISPLAY_X and DISPLAY_Y as axis
  • Construct a MathTransform transforming from your Data's CoordinateReferenceSystem to the "screen"

It is often easier to treat the problem in two steps:

  1. Make a "viewport" model recording the location on the world you are displaying,
    • For your first cut capture this "viewport" using DefaultGeographicCRS.WGS84
  2. Construct a MathTransform from your data CRS to the "viewport" CRS:
    MathTransform data2world = CRS.findMathTransform( crs, DefaultGeographicCRS.WGS84 );
  3. Construct a MathTransform from your "viewport" CRS to the "screen" CRS (tip: in the OGC Geographic Objects specification, the "viewport" CRS is named objective CRS and the "screen" CRS is named display CRS):
    AffineTransform2D world2screen = new AffineTransform2D(); // start with identity transform
    world2screen.translate( viewport.minLong, viewport.maxLat ); // relocate to the viewport
    world2screen.scale( viewport.getWidth() / screen.width, viewport.getHeight() / screen.height ); // scale to fit
    world2screen.scale( 1.0, -1.0 ); // flip to match screen
  4. Combine the two MathTransforms
    MathTransform transform = defaultMathTransformFactory.createConcatenatedTransform( data2world, world2screen );
  5. Use the transform to modify your Geometry for the screen
    Geometry screenGeometry = geometry.transform( screenCRS, transform );
  6. Draw with confidence knowing that you are using DISPLAY_X and DISPLAY_Y
    final int X = 0;
    final int Y = 1;
    public void drawPoint( Graphics g, DirectPosition point ){
        int x = point.getOrdinate(X);
        int x = point.getOrdinate(Y);
        g.drawPoint(x, y);
    }

As long as you do this correctly everything will work out, you will know what "X" and you will have forced your data into DISPLAY_X and DISPLAY_Y.

From Screen to World

The above instructions seem to cause some confusion; it may be easier to take the inverse of your world2screen transform (usually you have one around since you were using it for drawing).

AffineTransform world2screen =
   RendererUtilities.worldToScreenTransform(mapContext.getLayerBounds(),
                                            new Rectangle(panelMap.getWidth(), panelMap.getHeight()));

AffineTransform screen2world = world2screen.createInverse();
Point2D pointScreen = screen2world.transform(pointScreenAbsolute, null);

When this is a Problem

This is a problem when you run into code that would like to assume that each DirectPosition contains data in (x,y) order (ie matching the screen).

There exist many methods that are almost helpful:

  • getHorizontalCRS return the GeographicCRS or ProjectedCRS part, or a DerivedCRS based on the above, that applies to the Earth's surface (ie real geophysical meaning - not the first two axis). You would still need to account for axis direction and polar coordinates on your own time.

The XY Assumption

A really common assumption for Java developers is to treat Geometry in exactly the same manner as Java2D Shape:

public void drawPoint( Graphics g, DirectPosition point ){
    int x = (point.getOrdinate(X) - dx) * scale;
    int y = height - ((point.getOrdinate(Y) - dy) * scale);
    
    g.drawPoint(x, y);
}

This code can be improved in several ways:

  • "x" is assumed to be ordinate(0), "y" is assumed to be ordinate(1)
  • A complicated transform is being performed by hand, "y" is inverted to match screen orientation, a transform is specified using dx and dy offsets and the entire result is scaled

This code will fail when presented with:

  • data in "y"/"x" order
  • data in which the direction of the axis is not what was expected
  • data that was collected in polar coordinates

Please note that non public geotools classes (such as CRSUtiities.getCRS2D) often make use of this assumption; blinding returning the first 2 dimensions no matter what they are.

A Quick Hack

If your data agrees with the above assumptions, you can make use of a quick hack:

public void drawPoint( Graphics g, DirectPosition point ){
   final int X = 0;
   final int Y = 1;

   int x = (point.getOrdinate(X) - dx) * scale;
   int y = height - ((point.getOrdinate(Y) - dy)*scale);

   g.drawPoint(x, y);
}

You will also need to provided a set of global hints:

public void static main(String args[] ){
   Map config = new HashMap();
   config.put( Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, true );
   config.put( Hints.FORCE_STANDARD_AXIS_DIRECTIONS, true );

   Hints hints = new Hints( config );
   GeoTools.init( hints ); // Set FactoryUsingWKT as the default
   
   ...application code...
}

GeoTools will now use CoordinateReferenceSystem objects that agree with your assumptions:

  • data is in (x,y) order
  • data is collected in the expected direction (ie. EAST and WEST are the same)

Looking up the right Axis

The following will allow you to math up to a correct axis:

public void drawPoint( Graphics g, DirectPosition point ){
   final int X = indexOfX( point.getCoordinateReferenceSystem() );
   final int Y = indexOfY( point.getCoordinateReferenceSystem() );

   int x = (point.getOrdinate(X) - dx) * scale;
   int y = height - ((point.getOrdinate(Y) - dy)*scale);

   g.drawPoint(x, y);
}

Where the following has been defined:

private int indexOfX( CoordinateReferenceSystem crs ){
    Set<AxisDirection> up = new HashSet<AxisDirection>();
    up.add( AxisDirection.DISPLAY_LEFT );
    up.add( AxisDirection.EAST );
    up.add( AxisDirection.GEOCENTRIC_X );
    up.add( AxisDirection.COLUMN_POSITIVE );
    return indexOf( cs, up );
}
private int indexOfX( CoordinateReferenceSystem crs ){
    Set<AxisDirection> up = new HashSet<AxisDirection>();
    up.add( AxisDirection.DISPLAY_UP );
    up.add( AxisDirection.NORTH );
    up.add( AxisDirection.GEOCENTRIC_Y );
    up.add( AxisDirection.ROW_POSITIVE );
    return indexOf( cs, up );
}
private int indexOf( CoordinateReferenceSystem crs, Set<AxisDirection> direction ){
   CoordinateSystem cs = coordinateReferenceSystem.getCoordinateSystem();
   for( int index=0; index<cs.getDimension(); index++){
       CoordinateSystemAxis axis = cs.getAxis(i);
       if( direction.contains( axis.getDirection() ) return index;
   }   
   return -1;
}

This code will fail when presented with:

  • data in which the direction of the axis is not what was expected
  • data that was collected in polar coordinates

Please note that you will still miss out on a lot of data, we have only looked for AxisDirection that match our assumptions (ie that the data is across an increasing - such as EAST). We are missing out on other data that is obviously across but is decreasing - such as WEST.