Axis
----

A quick page to help take apart the ``CoordinateReferenceSystem`` object and figure out what the raw data is measuring.

This is most often phrased as the question "What axis is X?" on our mailing list.

Using Transform
^^^^^^^^^^^^^^^

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

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``::
   
     MathTransform data2world = CRS.findMathTransform( crs, DefaultGeographicCRS.WGS84 );
   
2. Construct a ``MathTransform`` from your "viewport" CRS to the "screen" 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
   
   In the OGC Geographic Objects specification, the "viewport" CRS is named objective
   CRS and the "screen" CRS is named display CRS).

3. Combine the two ``MathTransforms``::
     
     MathTransform transform = defaultMathTransformFactory.createConcatenatedTransform( data2world, world2screen );

4. Use the transform to modify your Geometry for the screen::
   
      Geometry screenGeometry = geometry.transform( screenCRS, transform );

5. 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);
     }

6. As long as you do this correctly everything will work out, you will know what "X" is
   (because you defined it) and you will have forced your data into ``DISPLAY_X`` and
   ``DISPLAY_Y``.

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

Avoid Assumptions
^^^^^^^^^^^^^^^^^

This is a problem when you run into code that would like to assume that each ``DirectPosition`` contains data in (x,y) order
(i.e. 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 (i.e. real geophysical meaning - not the first two axis).

* You would still need to account for axis direction and polar coordinates on your own time.

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 some GeoTools classes, such as ``CRSUtilities.getCRS2D``, often make use of this assumption; blindly
returning the first 2 dimensions no matter what they are.

Quick Fix
'''''''''

If you run into some information that has proceeded with the above assumptions
you can make a quick fix::
  
  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 do its best to create ``CoordinateReferenceSystem`` objects that agree with your assumptions:

* data is in (x,y) order
* data is collected in the expected direction (i.e. EAST and WEST are the same)

Lookup 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 (i.e. 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.
