use of org.opengis.referencing.operation.Matrix in project sis by apache.
the class TensorValues method toMatrix.
/**
* Creates a matrix from this group of parameters.
* This operation is allowed only for tensors of {@linkplain TensorParameters#rank() rank} 2.
*
* @return a matrix created from this group of parameters.
*/
final Matrix toMatrix() {
final int numRow = dimensions[0].intValue();
final int numCol = dimensions[1].intValue();
final Matrix matrix = Matrices.createDiagonal(numRow, numCol);
if (values != null) {
for (int j = 0; j < numRow; j++) {
final ParameterValue<?>[] row = (ParameterValue<?>[]) values[j];
if (row != null) {
for (int i = 0; i < numCol; i++) {
final ParameterValue<?> element = row[i];
if (element != null) {
matrix.setElement(j, i, element.doubleValue());
}
}
}
}
}
return matrix;
}
use of org.opengis.referencing.operation.Matrix in project sis by apache.
the class AffineTest method testWKT.
/**
* Tests WKT formatting, and in particular the adjustment according
* whether we comply with EPSG:9624 definition or not.
*/
@Test
@DependsOnMethod("testParameters")
public void testWKT() {
final Matrix matrix = Matrices.createDiagonal(3, 3);
assertWktEquals("PARAMETERGROUP[“Affine parametric transformation”," + " ID[“EPSG”, 9624]]", Affine.parameters(matrix));
/*
* Try arbitrary values.
*/
// A1
matrix.setElement(0, 1, 2);
// B1
matrix.setElement(1, 1, 0);
// B2
matrix.setElement(1, 2, -1);
assertWktEquals("PARAMETERGROUP[“Affine parametric transformation”,\n" + " PARAMETER[“A1”, 2.0, ID[“EPSG”, 8624]],\n" + " PARAMETER[“B1”, 0.0, ID[“EPSG”, 8640]],\n" + " PARAMETER[“B2”, -1.0, ID[“EPSG”, 8641]],\n" + " ID[“EPSG”, 9624]]", Affine.parameters(matrix));
/*
* Setting a value on the last row make the matrix non-affine.
* So it should not be anymore EPSG:9624.
*/
// C0
matrix.setElement(2, 0, 3);
assertWktEquals("PARAMETERGROUP[“Affine”,\n" + " PARAMETER[“num_row”, 3],\n" + " PARAMETER[“num_col”, 3],\n" + " PARAMETER[“elt_0_1”, 2.0],\n" + " PARAMETER[“elt_1_1”, 0.0],\n" + " PARAMETER[“elt_1_2”, -1.0],\n" + " PARAMETER[“elt_2_0”, 3.0]]", Affine.parameters(matrix));
}
use of org.opengis.referencing.operation.Matrix in project sis by apache.
the class TransformSeparator method filterSourceDimensions.
/**
* Creates a transform for the same mathematic than the given {@code step}
* but expecting only the given dimensions as inputs.
* This method is invoked by {@link #separate()} when user-specified source dimensions need to be taken in account.
* The given {@code step} and {@code dimensions} are typically the values of
* {@link #transform} and {@link #sourceDimensions} fields respectively, but not necessarily.
* In particular those arguments will differ when this method is invoked recursively for processing
* concatenated or {@linkplain PassThroughTransform#getSubTransform() sub-transforms}.
*
* <p>Subclasses can override this method if they need to handle some {@code MathTransform} implementations
* in a special way. However all implementations of this method shall obey to the following contract:</p>
* <ul class="verbose">
* <li>{@link #sourceDimensions} and {@link #targetDimensions} should not be assumed accurate
* since they may be temporarily outdated or modified during recursive calls to this method.</li>
* <li>{@link #sourceDimensions} should not be modified by this method.</li>
* <li>{@link #targetDimensions} <strong>must</strong> be <em>overwritten</em> (not updated) by this method to the
* sequence of all target dimensions of {@code step} that are also target dimensions of the returned transform.
* The indices shall be in strictly increasing order from 0 inclusive to
* {@code step.getTargetDimensions()} exclusive.</li>
* </ul>
*
* @param step the transform for which to retain only a subset of the source dimensions.
* @param dimensions indices of the source dimensions of {@code step} to retain.
* @return a transform expecting only the given source dimensions.
* @throws FactoryException if the given transform is not separable.
*/
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
protected MathTransform filterSourceDimensions(final MathTransform step, final int[] dimensions) throws FactoryException {
if (dimensions.length == 0) {
return IdentityTransform.create(0);
}
final int numSrc = step.getSourceDimensions();
final int numTgt = step.getTargetDimensions();
final int lower = dimensions[0];
final int upper = dimensions[dimensions.length - 1] + 1;
if (lower == 0 && upper == numSrc && dimensions.length == numSrc) {
targetDimensions = series(0, numTgt);
return step;
}
if (step.isIdentity()) {
targetDimensions = dimensions;
return IdentityTransform.create(dimensions.length);
}
if (step instanceof ConcatenatedTransform) {
final ConcatenatedTransform ctr = (ConcatenatedTransform) step;
final MathTransform step1 = filterSourceDimensions(ctr.transform1, dimensions);
final MathTransform step2 = filterSourceDimensions(ctr.transform2, targetDimensions);
return factory.createConcatenatedTransform(step1, step2);
// Keep the 'targetDimensions' computed by the last step.
}
/*
* Special case for the passthrough transform: if at least one input dimension belong to the pass-
* through sub-transform, then invoke this method recursively for the sub-transform dimensions.
*/
if (step instanceof PassThroughTransform) {
final PassThroughTransform passThrough = (PassThroughTransform) step;
final int numSubSrc = passThrough.subTransform.getSourceDimensions();
final int numNewDim = passThrough.subTransform.getTargetDimensions() - numSubSrc;
final int subLower = passThrough.firstAffectedOrdinate;
final int subUpper = subLower + numSubSrc;
int[] subDimensions = new int[dimensions.length];
targetDimensions = null;
int n = 0;
for (int dim : dimensions) {
if (dim >= subLower) {
if (dim < subUpper) {
// Dimension n belong to the subtransform.
subDimensions[n++] = dim - subLower;
continue;
}
dim += numNewDim;
}
/*
* Dimension n belong to heading or trailing dimensions.
* Passthrough, after adjustment for trailing dimensions.
*/
targetDimensions = insert(targetDimensions, dim);
}
subDimensions = ArraysExt.resize(subDimensions, n);
/*
* If no source dimension belong to the sub-transform, then all source dimensions are heading or
* trailing dimensions. A passthrough transform without its sub-transform is an identity transform.
*/
if (n == 0) {
return IdentityTransform.create(dimensions.length);
}
/*
* There is at least one dimension to separate in the sub-transform. Perform this separation and get
* the list of target dimensions. We need to offset the target dimensions by the amount of leading
* dimensions once the separation is done, in order to translate from the sub-transform's dimension
* numbering to the transform's numbering.
*/
int[] target = targetDimensions;
final MathTransform subTransform = filterSourceDimensions(passThrough.subTransform, subDimensions);
for (final int dim : targetDimensions) {
target = insert(target, dim + subLower);
}
targetDimensions = target;
/*
* If all source dimensions not in the sub-transform are consecutive numbers, we can use our passthrough
* transform implementation. The "consecutive numbers" requirement (expressed in the 'if' statement below)
* is a consequence of a limitation in our current implementation: our current passthrough transform does
* not accept arbitrary index for modified ordinates.
*/
if (containsAll(dimensions, lower, subLower) && containsAll(dimensions, subUpper, upper)) {
return factory.createPassThroughTransform(subLower - lower, subTransform, Math.max(0, upper - subUpper));
}
}
/*
* If the transform is affine (or at least projective), express the transform as a matrix. Then, select
* target dimensions that depend only on specified source dimensions. If a target dimension depends on
* at least one discarded source dimension, then that output dimension will be discarded as well.
*/
final Matrix matrix = MathTransforms.getMatrix(step);
if (matrix != null) {
targetDimensions = null;
int startOfRow = 0;
boolean isLastRowAccepted = false;
final int numFilteredColumns = (dimensions.length + 1);
double[] elements = new double[(numTgt + 1) * numFilteredColumns];
reduce: for (int j = 0; j <= numTgt; j++) {
/*
* For each target dimension (i.e. a matrix row), find the matrix elements (excluding translation
* terms in the last column) for each source dimension to be kept. If a dependancy to at least one
* discarded input dimension is found, then the whole output dimension is discarded.
*/
int filteredColumn = 0;
for (int i = 0; i < numSrc; i++) {
final double element = matrix.getElement(j, i);
if (filteredColumn < dimensions.length && dimensions[filteredColumn] == i) {
elements[startOfRow + filteredColumn++] = element;
} else if (element != 0) {
/*
* Output dimension 'j' depends on one of discarded input dimension 'i'.
* The whole row will be discarded.
*/
continue reduce;
}
}
// Copy the translation term.
elements[startOfRow + filteredColumn++] = matrix.getElement(j, numSrc);
// We should have used all values in the 'dimensions' array.
assert filteredColumn == numFilteredColumns : filteredColumn;
startOfRow += numFilteredColumns;
if (j == numTgt) {
/*
* In an affine transform, the last row is usually [0 0 0 … 1].
* This is not a real dimension, but nevertheless mandatory.
*/
isLastRowAccepted = true;
} else {
targetDimensions = insert(targetDimensions, j);
}
}
if (isLastRowAccepted) {
elements = ArraysExt.resize(elements, startOfRow);
return factory.createAffineTransform(Matrices.create(startOfRow / numFilteredColumns, numFilteredColumns, elements));
}
/*
* In an affine transform, the last row is not supposed to have dependency to any source dimension.
* But if we reach this point, our matrix has such dependencies.
*/
}
throw new FactoryException(Resources.format(Resources.Keys.NotAnAffineTransform));
}
use of org.opengis.referencing.operation.Matrix in project sis by apache.
the class DefaultConversionTest method createLongitudeRotation.
/**
* Creates a very simple conversion performing a longitude rotation.
* The source CRS shall use the Paris prime meridian and the target CRS the Greenwich prime meridian,
* at least conceptually. See {@link #createLongitudeRotation(boolean)} for an explanation about why
* this is not really a valid conversion.
*
* @param sourceCRS a CRS using the Paris prime meridian.
* @param targetCRS a CRS using the Greenwich prime meridian.
* @param interpolationCRS a dummy interpolation CRS, or {@code null} if none.
*/
private static DefaultConversion createLongitudeRotation(final GeographicCRS sourceCRS, final GeographicCRS targetCRS, final TemporalCRS interpolationCRS) {
/*
* The following code fills the parameter values AND creates itself the MathTransform instance
* (indirectly, through the matrix). The later step is normally not our business, since we are
* supposed to only fill the parameter values and let MathTransformFactory creates the transform
* from the parameters. But we don't do the normal steps here because this class is a unit test:
* we want to test DefaultConversion in isolation of MathTransformFactory.
*/
final int interpDim = ReferencingUtilities.getDimension(interpolationCRS);
final int sourceDim = sourceCRS.getCoordinateSystem().getDimension();
final int targetDim = targetCRS.getCoordinateSystem().getDimension();
final OperationMethod method = DefaultOperationMethodTest.create("Longitude rotation", "9601", "EPSG guidance note #7-2", sourceDim, DefaultParameterDescriptorTest.createEPSG("Longitude offset", (short) 8602));
final ParameterValueGroup pg = method.getParameters().createValue();
pg.parameter("Longitude offset").setValue(OFFSET);
final Matrix rotation = Matrices.createDiagonal(// Number of rows.
targetDim + interpDim + 1, // Number of columns.
sourceDim + interpDim + 1);
rotation.setElement(interpDim, interpDim + sourceDim, OFFSET);
/*
* In theory we should not need to provide the parameters explicitly to the constructor since
* we are supposed to be able to find them from the MathTransform. But in this simple test we
* did not bothered to define a specialized MathTransform class for our case. So we will help
* a little bit DefaultConversion by telling it the parameters that we used.
*/
final Map<String, Object> properties = new HashMap<>(4);
properties.put(DefaultTransformation.NAME_KEY, "Paris to Greenwich");
properties.put(ReferencingServices.PARAMETERS_KEY, pg);
return new DefaultConversion(properties, sourceCRS, targetCRS, interpolationCRS, method, MathTransforms.linear(rotation));
}
use of org.opengis.referencing.operation.Matrix in project sis by apache.
the class LinearTransformBuilderTest method test2D.
/**
* Implementation of {@link #testExact2D()} and {@link #testNonExact2D()}.
*
* @param rd the random number generator to use.
* @param numPts the number of points to generate.
* @param addErrors {@code true} for adding a random error in the target points.
* @param scaleTolerance tolerance threshold for floating point comparisons.
*/
private static void test2D(final Random rd, final int numPts, final boolean addErrors, final double scaleTolerance, final double translationTolerance) throws FactoryException {
/*
* Create an AffineTransform to use as the reference implementation.
*/
final AffineTransform ref = AffineTransform.getRotateInstance(// Rotation angle
rd.nextDouble() * (2 * StrictMath.PI), // Center X
rd.nextDouble() * 30 - 12, // Center Y
rd.nextDouble() * 10 - 8);
final Map<DirectPosition2D, DirectPosition2D> pos = new HashMap<>(numPts);
for (int i = 0; i < numPts; i++) {
final DirectPosition2D src = new DirectPosition2D(rd.nextDouble() * 100 - 50, rd.nextDouble() * 200 - 75);
final DirectPosition2D tgt = new DirectPosition2D();
assertSame(tgt, ref.transform(src, tgt));
if (addErrors) {
tgt.x += rd.nextDouble() * 10 - 5;
tgt.y += rd.nextDouble() * 10 - 5;
}
assertNull(pos.put(src, tgt));
}
/*
* Create the fitted transform to test.
*/
final LinearTransformBuilder builder = new LinearTransformBuilder();
builder.setControlPoints(pos);
final Matrix m = builder.create(null).getMatrix();
/*
* Compare the coefficients with the reference implementation.
*/
assertEquals("m₀₀", ref.getScaleX(), m.getElement(0, 0), scaleTolerance);
assertEquals("m₀₁", ref.getShearX(), m.getElement(0, 1), scaleTolerance);
assertEquals("m₀₂", ref.getTranslateX(), m.getElement(0, 2), translationTolerance);
assertEquals("m₁₀", ref.getShearY(), m.getElement(1, 0), scaleTolerance);
assertEquals("m₁₁", ref.getScaleY(), m.getElement(1, 1), scaleTolerance);
assertEquals("m₁₂", ref.getTranslateY(), m.getElement(1, 2), translationTolerance);
assertArrayEquals("correlation", new double[] { 1, 1 }, builder.correlation(), scaleTolerance);
}
Aggregations