/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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.geotools.parameter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.measure.unit.Unit;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.InvalidParameterNameException;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.operation.Matrix;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.operation.matrix.MatrixFactory;
import org.geotools.resources.UnmodifiableArrayList;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
/**
* A parameter group for {@linkplain Matrix matrix} elements. The amount of
* {@linkplain ParameterValue parameter values} is extensible, i.e. it can grown or
* shrink according the value of <code>"num_row"</code> and <code>"num_col"</code>
* parameters. The parameters format may vary according the information provided to
* the constructor, but it is typically as below:
* <blockquote><pre>
* num_row
* num_col
* elt_0_0
* elt_0_1
* ...
* elt_0_<var><num_col-1></var>
* elt_1_0
* elt_1_1
* ...
* elt_<var><num_row-1></var>_<var><num_col-1></var>
* </pre></blockquote>
*
* @since 2.1
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see MatrixParameters
*/
public class MatrixParameterDescriptors extends DefaultParameterDescriptorGroup {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -7386537348359343836L;
/**
* The default matrix size for the
* {@linkplain #MatrixParameterDescriptors(Map) one-argument constructor}.
*/
public static final int DEFAULT_MATRIX_SIZE = 3;
/**
* The height and weight of the matrix of {@link #parameters} to cache. Descriptors
* for row or column indices greater than or equals to this value will not be cached.
*/
private static final int CACHE_SIZE = 8;
/**
* The cached descriptors for each elements in a matrix. Descriptors do not depends
* on matrix element values. Consequently, the same descriptors can be reused for all
* {@link MatrixParameters} instances.
*/
@SuppressWarnings("unchecked")
private final ParameterDescriptor<Double>[] parameters =
new ParameterDescriptor[CACHE_SIZE * CACHE_SIZE];
/**
* The descriptor for the {@code "num_row"} parameter.
*/
protected final ParameterDescriptor<Integer> numRow;
/**
* The descriptor for the {@code "num_col"} parameter.
*/
protected final ParameterDescriptor<Integer> numCol;
/**
* The prefix to insert in front of parameter name for each matrix elements.
*/
protected final String prefix;
/**
* The separator between the row and the column index in parameter names.
*/
protected final char separator;
/**
* Constructs a parameter group with default name format matching
* <cite><A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well
* Known Text</A></cite> usages.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
*/
public MatrixParameterDescriptors(final Map<String,?> properties) {
/*
* Note: the upper limit given in the operation parameters is arbitrary. A high
* value doesn't make much sense anyway since matrix size for projective
* transform will usually not be much more than 5, and the storage scheme
* used in this implementation is inefficient for large amount of matrix
* elements.
*/
this(properties, new ParameterDescriptor[] {
DefaultParameterDescriptor.create("num_row", DEFAULT_MATRIX_SIZE, 2, 50),
DefaultParameterDescriptor.create("num_col", DEFAULT_MATRIX_SIZE, 2, 50)
}, "elt_", '_');
}
/**
* Constructs a parameter group. The properties map is given unchanged to the
* {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
* The {@code parameters} array should contains parameters <strong>other</strong>
* than matrix elements. The first parameter is assumed to be the number of rows, and
* the second parameter the number of columns. All extra parameters are ignored.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
* @param parameters The {@code "num_row"} and {@code "num_col"} parameters.
* @param prefix The prefix to insert in front of parameter name for each matrix elements.
* @param separator The separator between the row and the column index in parameter names.
*/
public MatrixParameterDescriptors(final Map<String,?> properties,
ParameterDescriptor<?>[] parameters,
final String prefix,
final char separator)
{
super(properties, parameters);
if (parameters.length < 2) {
// TODO: provide a localized message
throw new IllegalArgumentException();
}
numRow = Parameters.cast(parameters[0], Integer.class);
numCol = Parameters.cast(parameters[1], Integer.class);
ensureNonNull("prefix", prefix);
this.prefix = prefix;
this.separator = separator;
}
/**
* Verify that the specified index is included in the expected range of values.
*
* @param name The parameter name. To be used for formatting error message.
* @param index The indice to check.
* @param upper The upper range value, exclusive.
* @throws IndexOutOfBoundsException if {@code index} is outside the expected range.
*/
static void checkIndice(final String name, final int index, final int upper)
throws IndexOutOfBoundsException
{
if (index<0 || index>=upper) {
throw new IndexOutOfBoundsException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, name, index));
}
}
/**
* Returns the parameter in this group for the specified name. The name can be a matrix element
* if it uses the following syntax: <code>"elt_<var>row</var>_<var>col</var>"</code> where
* {@code "elt_"} is the {@linkplain #prefix} for all matrix elements, and <var>row</var>
* and <var>col</var> are row and column indices respectively. For example {@code "elt_2_1"}
* is the element name for the value at line 2 and row 1. The row and column index are 0 based.
*
* @param name The case insensitive name of the parameter to search for.
* @return The parameter for the given name.
* @throws ParameterNotFoundException if there is no parameter for the given name.
*/
@Override
@SuppressWarnings("unchecked")
public final GeneralParameterDescriptor descriptor(final String name)
throws ParameterNotFoundException
{
return descriptor(name, ((Number) numRow.getMaximumValue()).intValue(),
((Number) numCol.getMaximumValue()).intValue());
}
/**
* Implementation of the {@link #descriptor(String)} method.
*
* @param name The case insensitive name of the parameter to search for.
* @param numRow The maximum number of rows.
* @param numCol The maximum number of columns.
* @return The parameter for the given name.
* @throws ParameterNotFoundException if there is no parameter for the given name.
*/
final GeneralParameterDescriptor descriptor(String name, final int numRow, final int numCol)
throws ParameterNotFoundException
{
ensureNonNull("name", name);
name = name.trim();
RuntimeException cause = null;
if (name.regionMatches(true, 0, prefix, 0, prefix.length())) {
final int split = name.indexOf(separator, prefix.length());
if (split >= 0) try {
final int row = Integer.parseInt(name.substring(prefix.length(), split));
final int col = Integer.parseInt(name.substring(split+1));
return descriptor(row, col, numRow, numCol);
} catch (NumberFormatException exception) {
cause = exception;
} catch (IndexOutOfBoundsException exception) {
cause = exception;
}
}
/*
* The parameter name is not a matrix element name. Search in the super
* class for other parameters, especially "num_row" and "num_col".
*/
try {
return super.descriptor(name);
} catch (ParameterNotFoundException exception) {
if (cause != null) try {
exception.initCause(cause);
} catch (IllegalStateException ignore) {
// A cause has already be given to the exception. Forget the cause then.
}
throw exception;
}
}
/**
* Returns the parameter in this group for a matrix element at the specified
* index. row and column indices are 0 based. Indices must be lower that the
* {@linkplain ParameterDescriptor#getMaximumValue maximum values}
* given to the {@link #numRow} and {@link #numCol} parameters.
*
* @param row The row indice.
* @param column The column indice
* @return The parameter descriptor for the specified matrix element.
* @throws IndexOutOfBoundsException if {@code row} or {@code column} is out of bounds.
*/
@SuppressWarnings("unchecked")
public final ParameterDescriptor<Double> descriptor(final int row, final int column)
throws IndexOutOfBoundsException
{
return descriptor(row, column, ((Number) numRow.getMaximumValue()).intValue(),
((Number) numCol.getMaximumValue()).intValue());
}
/**
* Implementation of the {@link #descriptor(int,int)} method.
*
* @param row The row indice.
* @param column The column indice
* @param numRow The maximum number of rows.
* @param numCol The maximum number of columns.
* @return The parameter descriptor for the specified matrix element.
* @throws IndexOutOfBoundsException if {@code row} or {@code column} is out of bounds.
*/
final ParameterDescriptor<Double> descriptor(final int row, final int column,
final int numRow, final int numCol)
throws IndexOutOfBoundsException
{
checkIndice("row", row, numRow);
checkIndice("column", column, numCol);
int index = -1;
ParameterDescriptor<Double> param;
if (row<CACHE_SIZE && column<CACHE_SIZE) {
index = row*CACHE_SIZE + column;
param = parameters[index];
if (param != null) {
return param;
}
}
/*
* Parameter not found in the cache. Create a new one and cache it for future reuse.
* Note that this cache is shared by all MatrixParameterDescriptors instance. There
* is no need to synchronize since it is not a big deal if the same parameter is
* constructed twice.
*/
param = new DefaultParameterDescriptor<Double>(
Collections.singletonMap(NAME_KEY, prefix + row + separator + column),
Double.class, null, (row == column) ? 1.0 : 0.0,
null, null, Unit.ONE, true);
if (index >= 0) {
parameters[index] = param;
}
return param;
}
/**
* Returns the parameters in this group. The number or elements is inferred from the
* {@linkplain ParameterDescriptor#getDefaultValue default values}
* given to the {@link #numRow} and {@link #numCol} parameters.
*
* @return The matrix parameters, including all elements.
*/
@Override
public final List<GeneralParameterDescriptor> descriptors() {
return descriptors(this.numRow.getDefaultValue().intValue(),
this.numCol.getDefaultValue().intValue());
}
/**
* Implementation of the {@link #descriptors()} method.
* Returns the parameters in this group for a matrix of the specified size.
*
* @param numRow The number of rows.
* @param numCol The number of columns.
* @return The matrix parameters, including all elements.
*/
final List<GeneralParameterDescriptor> descriptors(final int numRow, final int numCol) {
final GeneralParameterDescriptor[] parameters = new GeneralParameterDescriptor[numRow*numCol + 2];
int k = 0;
parameters[k++] = this.numRow;
parameters[k++] = this.numCol;
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
parameters[k++] = descriptor(j,i, numRow, numCol);
}
}
assert k == parameters.length : k;
return UnmodifiableArrayList.wrap(parameters);
}
/**
* Creates a new instance of {@linkplain MatrixParameters parameter values} with
* elements initialized to the 1 on the diagonal, and 0 everywere else. The returned
* parameter group is extensible, i.e. the number of elements will depends upon the
* value associated to the {@link #numRow} and {@link #numCol numCol} parameters.
*
* @return A new parameter initialized to the default value.
*/
@Override
public ParameterValueGroup createValue() {
return new MatrixParameters(this);
}
/**
* Constructs a matrix from a group of parameters.
*
* @param parameters The group of parameters.
* @return A matrix constructed from the specified group of parameters.
* @throws InvalidParameterNameException if a parameter name was not recognized.
*/
public Matrix getMatrix(final ParameterValueGroup parameters)
throws InvalidParameterNameException
{
if (parameters instanceof MatrixParameters) {
// More efficient implementation
return ((MatrixParameters) parameters).getMatrix();
}
// Fallback on the general case (others implementations)
final ParameterValue numRowParam = parameters.parameter(numRow.getName().toString());
final ParameterValue numColParam = parameters.parameter(numCol.getName().toString());
final int numRow = numRowParam.intValue();
final int numCol = numColParam.intValue();
final Matrix matrix = MatrixFactory.create(numRow, numCol);
final List<GeneralParameterValue> params = parameters.values();
if (params != null) {
for (final GeneralParameterValue param : params) {
if (param==numRowParam || param==numColParam) {
continue;
}
RuntimeException cause = null;
final String name = param.getDescriptor().getName().toString();
if (name.regionMatches(true, 0, prefix, 0, prefix.length())) {
final int split = name.indexOf(separator, prefix.length());
if (split >= 0) try {
final int row = Integer.parseInt(name.substring(prefix.length(), split));
final int col = Integer.parseInt(name.substring(split+1));
matrix.setElement(row, col, ((ParameterValue) param).doubleValue());
continue;
} catch (NumberFormatException exception) {
cause = exception;
} catch (IndexOutOfBoundsException exception) {
cause = exception;
}
}
final InvalidParameterNameException exception;
exception = new InvalidParameterNameException(Errors.format(
ErrorKeys.UNEXPECTED_PARAMETER_$1, name), name);
if (cause != null) {
exception.initCause(cause);
}
throw exception;
}
}
return matrix;
}
/**
* Compares the specified object with this parameter group for equality.
*
* @param object The object to compare to {@code this}.
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
if (super.equals(object, compareMetadata)) {
final MatrixParameterDescriptors that = (MatrixParameterDescriptors) object;
return this.separator == that.separator && Utilities.equals(this.prefix, that.prefix);
}
return false;
}
/**
* Returns a hash value for this parameter.
*
* @return The hash code value. This value doesn't need to be the same
* in past or future versions of this class.
*/
@Override
public int hashCode() {
return super.hashCode() ^ prefix.hashCode() ^ 37*separator;
}
}