Search code examples
javageometrygeotoolsgeography

How to use GeoTools MathTransform?


I need to transform points from EPSG:4312 to WGS84 using GeoTools Java library. But I'm not sure if I use it correctly, also I'm not sure it provides correct results.

See the following code sample:

    @Test
    public void testSingle4312ToWGS84() throws FactoryException, TransformException {
        double latitude = 29.0;
        double longitude = -99.0;

        CoordinateReferenceSystem sourceCrs = CRS.decode("EPSG:4312");
        CoordinateReferenceSystem targetCrs = CRS.decode("EPSG:4326");
        MathTransform engine = CRS.findMathTransform(sourceCrs, targetCrs, false);

        DirectPosition source = new DirectPosition2D(sourceCrs, longitude, latitude);
        DirectPosition target = new DirectPosition2D(targetCrs);
        engine.transform(source, target);

        System.out.println("x,y=" + target.getCoordinate()[0] + "," + target.getCoordinate()[1]);
    }

When running above code I get the following results:

x,y=-81.00483829765083,-150.99434404015307

Which is wrong, because -150 is not a valid latitude.

When I created the source object, I initialized X with longitude and Y with latitude, following the rationale that actually X is longitude and Y is latitude (according to the geographic community). However, if I reverse them:

DirectPosition source = new DirectPosition2D(sourceCrs, latitude, longitude);

I now get a result that looks more real:

x,y=29.003866857343915,-98.99222530180596

Of course I have to interpret X as latitude and Y as longitude now.

So, question #1, should we go with the inverse rational treating X as latitude and Y as longitude?

Next, I wanted to validate the result. First, trying to convert the same point using an Oracle SQL SDO function:

select sdo_cs.transform (
MDSYS.SDO_GEOMETRY(2001, 4312, MDSYS.SDO_POINT_TYPE(-99, 29, NULL), NULL, NULL),
8307
) from dual;

provides the following result:

MDSYS.SDO_GEOMETRY(2001, 8307, MDSYS.SDO_POINT_TYPE(-98.9924748682774, 29.0036029261273, NULL), NULL, NULL)

The converted point is similar with the one produced by GeoTools, but there is a difference of ~40 meters.

Next, I tried the transformation offered by this website, which provided the same results as Oracle. I also used Luciad AIXM Viewer, a graphical representation tool for GML, and this also displays my point at the same position computed by Oracle.

So, I have 3 independent tools providing the same result point which is 40 meters away from the one computed by GeoTools. Question #2, why is Geotools failing? Is Geotools reliable for CRS transformations?


Solution

  • For your first issue with axis order you have fallen for a common beginner's problem of assuming that you know what the axis order of a projection is, and in the case of ESPG:4326 that it is fixed.

    For example, the following code:

    CoordinateReferenceSystem targetCrs = CRS.decode("EPSG:4326");
    
    System.out.println("EPSG:4326 - " + CRS.getAxisOrder(targetCrs));
    System.out.println("WGS84 - " + CRS.getAxisOrder(DefaultGeographicCRS.WGS84));
    

    gives this output:

    EPSG:4326 - NORTH_EAST
    WGS84 - EAST_NORTH
    

    But if you add the line Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); the output becomes:

    EPSG:4326 - EAST_NORTH
    WGS84 - EAST_NORTH
    

    So, in general you should never rely on a "known" axis order in your code, instead using something like:

    double latitude = 29.0;
    double longitude = -99.0;
    
    CoordinateReferenceSystem sourceCrs = CRS.decode("EPSG:4312");
    CoordinateReferenceSystem targetCrs = CRS.decode("EPSG:4326");
    
    MathTransform engine = CRS.findMathTransform(sourceCrs, targetCrs, false);
    
    DirectPosition2D source;
    if (CRS.getAxisOrder(sourceCrs).equals(org.geotools.referencing.CRS.AxisOrder.EAST_NORTH)) {
      source = new DirectPosition2D(sourceCrs, longitude, latitude);
    } else {
      source = new DirectPosition2D(sourceCrs, latitude, longitude);
    }
    DirectPosition target = new DirectPosition2D(targetCrs);
    engine.transform(source, target);
    
    if (CRS.getAxisOrder(targetCrs).equals(org.geotools.referencing.CRS.AxisOrder.EAST_NORTH)) {
      System.out.println("lon,lat=" + target.getCoordinate()[0] + "," + target.getCoordinate()[1]);
    } else {
      System.out.println("lon,lat=" + target.getCoordinate()[1] + "," + target.getCoordinate()[0]);
    }
    

    As for the accuracy of the transform that would depend on which referencing module you imported and which CRS definition Oracle are using. With the gt-epsg-hsql module loaded I see the same ToWGS84[601.705, 84.263, 485.227, 4.7354, -1.3145, -5.393, -2.3887] matrix as the most accurate transform provided in the EPSG registry. If there is an NTv2 transformation available then adding that to your project should improve precision.