/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/exse/
lat/lon GmbH
http://www.lat-lon.de
It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
(C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite
Institut de Recherche pour le D�veloppement / US-Espace
mailto:seasnet@teledetection.fr
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; either
version 2.1 of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: klaus.greve@uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.model.csct.pt;
// OpenGIS dependencies (SEAGIS)
import java.awt.geom.AffineTransform;
import java.text.FieldPosition;
import java.text.NumberFormat;
import javax.vecmath.GMatrix;
import org.deegree.model.csct.cs.AxisOrientation;
import org.deegree.model.csct.resources.Utilities;
import org.deegree.model.csct.resources.css.ResourceKeys;
import org.deegree.model.csct.resources.css.Resources;
/**
* A two dimensional array of numbers.
* Row and column numbering begins with zero.
*
* @version 1.00
* @author OpenGIS (www.opengis.org)
* @author Martin Desruisseaux
*
* @see org.opengis.pt.PT_Matrix
* @see javax.vecmath.GMatrix
* @see java.awt.geom.AffineTransform
* @see javax.media.jai.PerspectiveTransform
* @see javax.media.j3d.Transform3D
* @see <A HREF="http://math.nist.gov/javanumerics/jama/">Jama matrix</A>
* @see <A HREF="http://jcp.org/jsr/detail/83.jsp">JSR-83 Multiarray package</A>
*/
public class Matrix extends GMatrix
{
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 3126899762163038129L;
/**
* Construct a square identity matrix of size
* <code>size</code> × <code>size</code>.
*/
public Matrix(final int size)
{super(size,size);}
/**
* Construct a matrix of size
* <code>numRow</code> × <code>numCol</code>.
* Elements on the diagonal <var>j==i</var> are set to 1.
*/
public Matrix(final int numRow, final int numCol)
{super(numRow, numCol);}
/**
* Constructs a <code>numRow</code> × <code>numCol</code> matrix
* initialized to the values in the <code>matrix</code> array. The array values
* are copied in one row at a time in row major fashion. The array should be
* exactly <code>numRow*numCol</code> in length. Note that because row and column
* numbering begins with zero, <code>row</code> and <code>numCol</code> will be
* one larger than the maximum possible matrix index values.
*/
public Matrix(final int numRow, final int numCol, final double[] matrix)
{
super(numRow, numCol, matrix);
if (numRow*numCol != matrix.length)
{
throw new IllegalArgumentException(String.valueOf(matrix.length));
}
}
/**
* Constructs a new matrix from a two-dimensional array of doubles.
*
* @param matrix Array of rows. Each row must have the same length.
* @throws IllegalArgumentException if the specified matrix is not regular
* (i.e. if all rows doesn't have the same length).
*/
public Matrix(final double[][] matrix) throws IllegalArgumentException
{
super(matrix.length, (matrix.length!=0) ? matrix[0].length : 0);
final int numRow = getNumRow();
final int numCol = getNumCol();
for (int j=0; j<numRow; j++)
{
if (matrix[j].length!=numCol)
{
throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_MATRIX_NOT_REGULAR));
}
setRow(j, matrix[j]);
}
}
/**
* Constructs a new matrix and copies the initial
* values from the parameter matrix.
*/
public Matrix(final GMatrix matrix)
{super(matrix);}
/**
* Construct a 3×3 matrix from
* the specified affine transform.
*/
public Matrix(final AffineTransform transform)
{
super(3,3, new double[]
{
transform.getScaleX(), transform.getShearX(), transform.getTranslateX(),
transform.getShearY(), transform.getScaleY(), transform.getTranslateY(),
0, 0, 1
});
}
/**
* Construct an affine transform mapping a source region to a destination
* region. The regions must have the same number of dimensions, but their
* axis order and axis orientation may be different.
*
* @param srcRegion The source region.
* @param srcAxis Axis orientation for each dimension of the source region.
* @param dstRegion The destination region.
* @param dstAxis Axis orientation for each dimension of the destination region.
* @param validRegions <code>true</code> if source and destination regions must
* be taken in account. If <code>false</code>, then source and destination
* regions will be ignored and may be null.
*/
private Matrix(final Envelope srcRegion, final AxisOrientation[] srcAxis,
final Envelope dstRegion, final AxisOrientation[] dstAxis,
final boolean validRegions)
{
this(srcAxis.length+1);
/*
* Arguments check. NOTE: those exceptions are catched
* by 'org.deegree.model.csct.ct.CoordinateTransformationFactory'.
* If exception type change, update the factory class.
*/
final int dimension = srcAxis.length;
if (dstAxis.length != dimension)
{
throw new MismatchedDimensionException(dimension, dstAxis.length);
}
if (validRegions)
{
srcRegion.ensureDimensionMatch(dimension);
dstRegion.ensureDimensionMatch(dimension);
}
/*
* Map source axis to destination axis. If no axis is moved (for example if the user
* want to transform (NORTH,EAST) to (SOUTH,EAST)), then source and destination index
* will be equal. If some axis are moved (for example if the user want to transform
* (NORTH,EAST) to (EAST,NORTH)), then ordinates at index <code>srcIndex</code> will
* have to be moved at index <code>dstIndex</code>.
*/
setZero();
for (int srcIndex=0; srcIndex<dimension; srcIndex++)
{
boolean hasFound = false;
final AxisOrientation srcAxe = srcAxis[srcIndex];
final AxisOrientation search = srcAxe.absolute();
for (int dstIndex=0; dstIndex<dimension; dstIndex++)
{
final AxisOrientation dstAxe = dstAxis[dstIndex];
if (search.equals(dstAxe.absolute()))
{
if (hasFound)
{
throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_COLINEAR_AXIS_$2,
srcAxe.getName(null), dstAxe.getName(null)));
}
hasFound = true;
/*
* Set the matrix elements. Some matrix elements will never
* be set. They will be left to zero, which is their wanted
* value.
*/
final boolean normal = srcAxe.equals(dstAxe);
double scale = (normal) ? +1 : -1;
double translate = 0;
if (validRegions)
{
translate = (normal) ? dstRegion.getMinimum(dstIndex) : dstRegion.getMaximum(dstIndex);
scale *= dstRegion.getLength (dstIndex) / srcRegion.getLength (srcIndex);
translate -= srcRegion.getMinimum(srcIndex)*scale;
}
setElement(dstIndex, srcIndex, scale);
setElement(dstIndex, dimension, translate);
}
}
if (!hasFound)
{
throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_NO_DESTINATION_AXIS_$1,
srcAxis[srcIndex].getName(null)));
}
}
setElement(dimension, dimension, 1);
}
/**
* Construct an affine transform changing axis order and/or orientation.
* For example, the affine transform may convert (NORTH,WEST) coordinates
* into (EAST,NORTH). Axis orientation can be inversed only. For example,
* it is illegal to transform (NORTH,WEST) coordinates into (NORTH,DOWN).
*
* @param srcAxis The set of axis orientation for source coordinate system.
* @param dstAxis The set of axis orientation for destination coordinate system.
* @throws MismatchedDimensionException if <code>srcAxis</code> and <code>dstAxis</code> don't have the same length.
* @throws IllegalArgumentException if the affine transform can't be created for some other raison.
*/
public static Matrix createAffineTransform(final AxisOrientation[] srcAxis, final AxisOrientation[] dstAxis)
{return new Matrix(null, srcAxis, null, dstAxis, false);}
/**
* Construct an affine transform that maps
* a source region to a destination region.
* Axis order and orientation are left unchanged.
*
* @param srcRegion The source region.
* @param dstRegion The destination region.
* @throws MismatchedDimensionException if regions don't have the same dimension.
*/
public static Matrix createAffineTransform(final Envelope srcRegion, final Envelope dstRegion)
{
final int dimension = srcRegion.getDimension();
dstRegion.ensureDimensionMatch(dimension);
final Matrix matrix = new Matrix(dimension+1);
for (int i=0; i<dimension; i++)
{
final double scale = dstRegion.getLength (i) / srcRegion.getLength (i);
final double translate = dstRegion.getMinimum(i) - srcRegion.getMinimum(i)*scale;
matrix.setElement(i, i, scale);
matrix.setElement(i, dimension, translate);
}
matrix.setElement(dimension, dimension, 1);
return matrix;
}
/**
* Construct an affine transform mapping a source region to a destination
* region. Axis order and/or orientation can be changed during the process.
* For example, the affine transform may convert (NORTH,WEST) coordinates
* into (EAST,NORTH). Axis orientation can be inversed only. For example,
* it is illegal to transform (NORTH,WEST) coordinates into (NORTH,DOWN).
*
* @param srcRegion The source region.
* @param srcAxis Axis orientation for each dimension of the source region.
* @param dstRegion The destination region.
* @param dstAxis Axis orientation for each dimension of the destination region.
* @throws MismatchedDimensionException if all arguments don't have the same dimension.
* @throws IllegalArgumentException if the affine transform can't be created for some other raison.
*/
public static Matrix createAffineTransform(final Envelope srcRegion, final AxisOrientation[] srcAxis,
final Envelope dstRegion, final AxisOrientation[] dstAxis)
{return new Matrix(srcRegion, srcAxis, dstRegion, dstAxis, true);}
/**
* Retrieves the specifiable values in the transformation matrix into a
* 2-dimensional array of double precision values. The values are stored
* into the 2-dimensional array using the row index as the first subscript
* and the column index as the second. Values are copied; changes to the
* returned array will not change this matrix.
*
* @see org.opengis.pt.PT_Matrix#elt
*/
public final double[][] getElements()
{
final int numCol = getNumCol();
final double[][] matrix = new double[getNumRow()][];
for (int j=0; j<matrix.length; j++)
{
getRow(j, matrix[j]=new double[numCol]);
}
return matrix;
}
/**
* Returns <code>true</code> if this matrix is an affine transform.
* A transform is affine if the matrix is square and last row contains
* only zeros, except in the last column which contains 1.
*/
public final boolean isAffine()
{
int dimension = getNumRow();
if (dimension != getNumCol())
return false;
dimension--;
for (int i=0; i<=dimension; i++)
if (getElement(dimension, i) != (i==dimension ? 1 : 0))
return false;
return true;
}
/**
* Returns <code>true</code> if this matrix is an identity matrix.
*/
public final boolean isIdentity()
{
final int numRow = getNumRow();
final int numCol = getNumCol();
if (numRow != numCol) return false;
for (int j=0; j<numRow; j++)
for (int i=0; i<numCol; i++)
if (getElement(j,i) != (i==j ? 1 : 0))
return false;
return true;
}
/**
* Returns an affine transform for this matrix.
* This is a convenience method for interoperability with Java2D.
*
* @throws IllegalStateException if this matrix is not 3x3,
* or if the last row is not [0 0 1].
*/
public final AffineTransform toAffineTransform2D() throws IllegalStateException
{
int check;
if ((check=getNumRow())!=3 || (check=getNumCol())!=3)
{
throw new IllegalStateException(Resources.format(ResourceKeys.ERROR_NOT_TWO_DIMENSIONAL_$1, new Integer(check-1)));
}
if (isAffine())
{
return new AffineTransform(getElement(0,0), getElement(1,0),
getElement(0,1), getElement(1,1),
getElement(0,2), getElement(1,2));
}
throw new IllegalStateException(Resources.format(ResourceKeys.ERROR_NOT_AN_AFFINE_TRANSFORM));
}
/**
* Returns a string representation of this matrix.
* The returned string is implementation dependent.
* It is usually provided for debugging purposes only.
*/
public String toString()
{
final int numRow = getNumRow();
final int numCol = getNumCol();
StringBuffer buffer = new StringBuffer(10000);
final int columnWidth = 12;
final String lineSeparator = System.getProperty("line.separator", "\n");
final FieldPosition dummy = new FieldPosition(0);
final NumberFormat format = NumberFormat.getNumberInstance();
format.setMinimumFractionDigits(6);
format.setMaximumFractionDigits(6);
for (int j=0; j<numRow; j++)
{
for (int i=0; i<numCol; i++)
{
final int position = buffer.length();
buffer = format.format(getElement(j,i), buffer, dummy);
buffer.insert(position, Utilities.spaces(columnWidth-(buffer.length()-position)));
}
buffer.append(lineSeparator);
}
return buffer.toString();
}
}