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
usingDISPLAY_X
andDISPLAY_Y
as axisConstruct a
MathTransform
transforming from your data’sCoordinateReferenceSystem
to the “screen”
It is often easier to treat the problem in two steps:
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 );
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).
Combine the two
MathTransforms
:MathTransform transform = defaultMathTransformFactory.createConcatenatedTransform( data2world, world2screen );
Use the transform to modify your Geometry for the screen:
Geometry screenGeometry = geometry.transform( screenCRS, transform );
Draw with confidence knowing that you are using
DISPLAY_X
andDISPLAY_Y
:final int X = 0; final int Y = 1; public void drawPoint( Graphics g, Position 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” is (because you defined it) and you will have forced your data into
DISPLAY_X
andDISPLAY_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¶
A common assumption is that each each Position
contains data in (x,y) order (i.e. matching the screen).
There exist many methods that to help:
getHorizontalCRS
return theGeographicCRS
orProjectedCRS
part, or aDerivedCRS
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, Position position ){
int x = (position.getOrdinate(X) - dx) * scale;
int y = height - ((position.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
anddy
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, Position position ){
final int X = 0;
final int Y = 1;
int x = (position.getOrdinate(X) - dx) * scale;
int y = height - ((position.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, Position position ){
final int X = indexOfX( position.getCoordinateReferenceSystem() );
final int Y = indexOfY( position.getCoordinateReferenceSystem() );
int x = (position.getOrdinate(X) - dx) * scale;
int y = height - ((position.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.