/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2010-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.referencing.operation.transform;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import java.util.Arrays;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import javax.media.jai.Warp;
import javax.media.jai.WarpGrid;
import javax.media.jai.WarpAffine;
import org.opengis.util.FactoryException;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.MathTransformFactory;
import org.geotoolkit.io.TableWriter;
import org.apache.sis.math.Statistics;
import org.geotoolkit.geometry.Envelopes;
import org.geotoolkit.factory.FactoryFinder;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.geotoolkit.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotoolkit.test.TestBase;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.test.DependsOn;
import org.junit.*;
import static org.junit.Assert.*;
import static java.lang.StrictMath.*;
/**
* Tests the {@link WarpFactory} class.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.16
*
* @since 3.16
*/
@DependsOn(WarpAdapterTest.class)
public final strictfp class WarpFactoryTest extends TestBase {
/**
* The tolerance threshold (in pixels) used by the {@link WarpFactory} being tested.
*/
private static final double TOLERANCE = 0.25;
/**
* The location and dimension (in pixels) of a pseudo-image to be projected.
*/
private final Rectangle imageBounds = new Rectangle(10, 20, 800, 600);
/**
* Tests the creation of an approximatively affine transform.
*
* @throws TransformException Should not happen.
*/
@Test
public void testAffine() throws TransformException {
final Rectangle bounds = new Rectangle(100, 200, 300, 400);
/*
* Trivial case: should be detected easily by the factory.
*/
MathTransform2D tr = new AffineTransform2D(2, 0, 0, 3, -10, -20);
final Warp warp1 = WarpFactory.DEFAULT.create(null, tr, bounds);
assertTrue("Expected a WarpAffine.", warp1 instanceof WarpAffine);
final float[][] coefficients = ((WarpAffine) warp1).getCoeffs();
assertTrue(Arrays.deepEquals(coefficients, new float[][] {
{-10, 2, 0},
{-20, 0, 3}
}));
/*
* Hide the fact that the transform is affine. It will require
* more work from the factory, but it should still detect that
* the transform is affine.
*/
tr = new PrivateTransform2D(tr);
final Warp warp2 = WarpFactory.DEFAULT.create(null, tr, bounds);
assertTrue("Expected a WarpAffine.", warp2 instanceof WarpAffine);
assertTrue(Arrays.deepEquals(coefficients, ((WarpAffine) warp2).getCoeffs()));
}
/**
* Creates a projection for the WGS84 ellipsoid using the default parameters.
*
* @param name The projection classification.
* @return The math transform implementing the given projection.
*/
private static MathTransform2D createProjection(final String name) throws FactoryException {
final MathTransformFactory factory = FactoryFinder.getMathTransformFactory(null);
final ParameterValueGroup param = factory.getDefaultParameters(name);
param.parameter("semi_major").setValue(CommonCRS.WGS84.ellipsoid().getSemiMajorAxis());
param.parameter("semi_minor").setValue(CommonCRS.WGS84.ellipsoid().getSemiMinorAxis());
param.parameter("latitude_of_origin").setValue(20);
return (MathTransform2D) factory.createParameterizedTransform(param);
}
/**
* Wraps the given projection in a transform from the source grid to the target grid.
*
* @param projection The projection to wrap.
* @param domain The domain in source coordinates (will be converted from geodetic to grid).
* @return The transform from source grid to target grid.
* @throws TransformException If an transform can not be created.
*/
private MathTransform2D createResampling(final MathTransform2D projection, final Rectangle2D domain)
throws TransformException
{
final GridToEnvelopeMapper mapper = new GridToEnvelopeMapper();
mapper.setGridExtent(imageBounds);
mapper.setEnvelope(domain);
final MathTransform2D pre = (MathTransform2D)
org.geotoolkit.referencing.operation.MathTransforms.linear(mapper.createAffineTransform());
mapper.setEnvelope(Envelopes.transform(projection, domain, null));
final MathTransform2D post = (MathTransform2D)
org.geotoolkit.referencing.operation.MathTransforms.linear(mapper.createAffineTransform()).inverse();
return MathTransforms.concatenate(pre, projection, post);
}
/**
* Compares the result of a warp with the result of a "reference" warp.
*
* @param expected The "reference" warp.
* @param tested The warp to test.
*/
private void compare(final String name, final MathTransform2D transform, final Warp tested) {
final Warp expected = new WarpAdapter(name, transform);
final Statistics sx = new Statistics("sx");
final Statistics sy = new Statistics("sy");
float[] expPt = null;
float[] tstPt = null;
final int xmin = imageBounds.x;
final int ymin = imageBounds.y;
final int xmax = imageBounds.width + xmin;
final int ymax = imageBounds.height + ymin;
for (int y=ymin; y<ymax; y++) {
for (int x=xmin; x<xmax; x++) {
expPt = expected.warpPoint(x, y, expPt);
tstPt = tested.warpPoint(x, y, tstPt);
assertEquals("Expected a two-dimensional point.", 2, expPt.length);
assertEquals("Expected a two-dimensional point.", 2, tstPt.length);
final double dx = abs(expPt[0] - tstPt[0]);
final double dy = abs(expPt[1] - tstPt[1]);
if (!(dx <= TOLERANCE && dy <= TOLERANCE)) {
fail("Error at (" + x + ',' + y + "): expected " +
Arrays.toString(expPt) + " but got " +
Arrays.toString(tstPt) + ". Error is (" + dx + ", " + dy + ')');
}
sx.accept(dx);
sy.accept(dy);
}
}
if (out != null) {
final TableWriter table = new TableWriter(null, TableWriter.SINGLE_VERTICAL_LINE);
table.setMultiLinesCells(true);
table.nextLine(TableWriter.SINGLE_HORIZONTAL_LINE);
table.write(sx.toString());
table.nextColumn();
table.write(sy.toString());
table.nextLine();
table.nextLine(TableWriter.SINGLE_HORIZONTAL_LINE);
out.println(name);
out.println(table);
out.println();
}
}
/**
* Tests using the Mercator projection.
*
* @throws FactoryException Should not happen.
* @throws TransformException Should not happen.
*/
@Test
@Ignore("MathTransformFactory not found for unknown reason. Will be revisited in Apache SIS.")
public void testMercator() throws FactoryException, TransformException {
final MathTransform2D projection = createProjection("Mercator_1SP");
final Rectangle2D.Double domain = new Rectangle2D.Double(-20, -40, 40, 80);
/*
* Try on a relatively large region, crossing the equator.
*/
MathTransform2D tr = createResampling(projection, domain);
Warp warp = WarpFactory.DEFAULT.create(null, tr, imageBounds);
assertTrue("Expected a WarpGrid.", warp instanceof WarpGrid);
assertEquals("The x dimension should be affine.", 1, ((WarpGrid) warp).getXNumCells());
assertEquals("The y dimension can not be affine.", 18, ((WarpGrid) warp).getYStep());
compare("Mercator using WarpGrid", tr, warp);
assertSame("Warp should be cached", warp, WarpFactory.DEFAULT.create(null, tr, imageBounds));
/*
* Try on a smaller region. Should be optimized to the affine transform case.
*/
domain.width = domain.height = 0.25; domain.y = 20;
tr = createResampling(projection, domain);
warp = WarpFactory.DEFAULT.create(null, tr, imageBounds);
assertTrue("Expected a WarpAffine.", warp instanceof WarpAffine);
compare("Mercator using WarpAffine", tr, warp);
assertSame("Warp should be cached", warp, WarpFactory.DEFAULT.create(null, tr, imageBounds));
}
/**
* Tests using the Lambert projection.
*
* @throws FactoryException Should not happen.
* @throws TransformException Should not happen.
*/
@Test
@Ignore("MathTransformFactory not found for unknown reason. Will be revisited in Apache SIS.")
public void testLambert() throws FactoryException, TransformException {
final MathTransform2D projection = createProjection("Lambert_Conformal_Conic_1SP");
final Rectangle2D.Double domain = new Rectangle2D.Double(-20, 40, 40, 20);
/*
* Try on a relatively large region, crossing the equator.
*/
MathTransform2D tr = createResampling(projection, domain);
Warp warp = WarpFactory.DEFAULT.create(null, tr, imageBounds);
assertTrue("Expected a WarpGrid.", warp instanceof WarpGrid);
assertEquals("The x dimension can not be affine.", 16, ((WarpGrid) warp).getXNumCells());
assertEquals("The y dimension can not be affine.", 37, ((WarpGrid) warp).getYStep());
compare("Lambert using WarpGrid", tr, warp);
assertSame("Warp should be cached", warp, WarpFactory.DEFAULT.create(null, tr, imageBounds));
/*
* Try on a smaller region. Should be optimized to the affine transform case.
*/
domain.width = domain.height = 0.125; domain.y = 50;
tr = createResampling(projection, domain);
warp = WarpFactory.DEFAULT.create(null, tr, imageBounds);
assertTrue("Expected a WarpAffine.", warp instanceof WarpAffine);
compare("Lambert using WarpAffine", tr, warp);
assertSame("Warp should be cached", warp, WarpFactory.DEFAULT.create(null, tr, imageBounds));
}
}