/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.referencing.operation; import java.util.HashMap; 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.InvalidParameterValueException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.Operation; import org.opengis.referencing.operation.Projection; import org.opengis.util.GenericName; import org.geotools.parameter.DefaultParameterDescriptor; import org.geotools.parameter.DefaultParameterDescriptorGroup; import org.geotools.parameter.Parameters; import org.geotools.resources.XArray; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.referencing.wkt.Formatter; import org.geotools.referencing.operation.transform.MathTransformProxy; /** * An {@linkplain DefaultOperationMethod operation method} capable to creates a * {@linkplain MathTransform math transform} from set of * {@linkplain GeneralParameterValue parameter values}. * Implementations of this class should be listed in the following file: * * <blockquote> * <P>{@code META-INF/services/org.geotools.referencing.operation.MathTransformProvider}</P> * </blockquote> * <P> * The {@linkplain DefaultMathTransformFactory math transform factory} will parse this file in order * to gets all available providers on a system. If this file is bundle in many JAR files, the * {@linkplain DefaultCoordinateOperationFactory math transform factory} will read all of them. * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public abstract class MathTransformProvider extends DefaultOperationMethod { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 7530475536803158473L; /** * Constructs a math transform provider from a set of parameters. The provider * {@linkplain #getIdentifiers identifiers} will be the same than the parameter * ones. * * @param sourceDimensions Number of dimensions in the source CRS of this operation method. * @param targetDimensions Number of dimensions in the target CRS of this operation method. * @param parameters The set of parameters (never {@code null}). */ public MathTransformProvider(final int sourceDimensions, final int targetDimensions, final ParameterDescriptorGroup parameters) { this(toMap(parameters), sourceDimensions, targetDimensions, parameters); } /** * Constructs a math transform provider from a set of properties. * The properties map is given unchanged to the * {@linkplain DefaultOperationMethod#DefaultOperationMethod(Map,int,int,ParameterDescriptorGroup) * super-class constructor}. * * @param properties Set of properties. Should contains at least {@code "name"}. * @param sourceDimensions Number of dimensions in the source CRS of this operation method. * @param targetDimensions Number of dimensions in the target CRS of this operation method. * @param parameters The set of parameters (never {@code null}). */ public MathTransformProvider(final Map<String,?> properties, final int sourceDimensions, final int targetDimensions, final ParameterDescriptorGroup parameters) { super(properties, sourceDimensions, targetDimensions, parameters); } /** * Work around for RFE #4093999 in Sun's bug database * ("Relax constraint on placement of this()/super() call in constructors"). */ private static Map<String,Object> toMap(final IdentifiedObject parameters) { ensureNonNull("parameters", parameters); final Map<String,Object> properties = new HashMap<String,Object>(4); properties.put(NAME_KEY, parameters.getName()); properties.put(IDENTIFIERS_KEY, parameters.getIdentifiers().toArray(EMPTY_IDENTIFIER_ARRAY)); properties.put(ALIAS_KEY, parameters.getAlias() .toArray(EMPTY_ALIAS_ARRAY)); return properties; } /** * Returns the operation type. It may be * <code>{@linkplain org.opengis.referencing.operation.Operation}.class</code>, * <code>{@linkplain org.opengis.referencing.operation.Conversion}.class</code>, * <code>{@linkplain org.opengis.referencing.operation.Projection}.class</code>, * <cite>etc</cite>. * <p> * The default implementation returns {@code Operation.class}. * Subclass should overrides this methods and returns the appropriate * OpenGIS interface type (<strong>not</strong> the implementation type). * * @return The GeoAPI interface implemented by this operation. */ @Override public Class<? extends Operation> getOperationType() { return Operation.class; } /** * Creates a math transform from the specified group of parameter values. * Subclasses can implements this method as in the example below: * * <blockquote><pre> * double semiMajor = values.parameter("semi_major").doubleValue(SI.METER); * double semiMinor = values.parameter("semi_minor").doubleValue(SI.METER); * // etc... * return new MyTransform(semiMajor, semiMinor, ...); * </pre></blockquote> * * @param values The group of parameter values. * @return The created math transform. * @throws InvalidParameterNameException if the values contains an unknow parameter. * @throws ParameterNotFoundException if a required parameter was not found. * @throws InvalidParameterValueException if a parameter has an invalid value. * @throws FactoryException if the math transform can't be created for some other reason * (for example a required file was not found). * * @see MathTransformProvider.Delegate */ protected abstract MathTransform createMathTransform(ParameterValueGroup values) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException, FactoryException; /** * Constructs a parameter descriptor from a set of alias. The parameter is * identified by codes provided by one or more authorities. Common authorities are * {@link Citations#OGC OGC} and {@link Citations#EPSG EPSG} for example. * * <P>The first entry in the {@code identifiers} array is both the * {@linkplain ParameterDescriptor#getName main name} and the * {@linkplain ParameterDescriptor#getIdentifiers identifiers}. * All others are {@linkplain ParameterDescriptor#getAlias aliases}.</P> * * @param identifiers The parameter identifiers. Most contains at least one entry. * @param defaultValue The default value for the parameter, or {@link Double#NaN} if none. * @param minimum The minimum parameter value, or {@link Double#NEGATIVE_INFINITY} if none. * @param maximum The maximum parameter value, or {@link Double#POSITIVE_INFINITY} if none. * @param unit The unit for default, minimum and maximum values. * @return The descriptor for the given identifiers. */ protected static ParameterDescriptor<Double> createDescriptor( final ReferenceIdentifier[] identifiers, final double defaultValue, final double minimum, final double maximum, final Unit<?> unit) { return DefaultParameterDescriptor.create(toMap(identifiers), defaultValue,minimum, maximum, unit, true); } /** * Constructs an optional parameter descriptor from a set of alias. * The parameter is identified as with {@link #createDescriptor}. * * @param identifiers The parameter identifiers. Most contains at least one entry. * @param minimum The minimum parameter value, or {@link Double#NEGATIVE_INFINITY} if none. * @param maximum The maximum parameter value, or {@link Double#POSITIVE_INFINITY} if none. * @param unit The unit for default, minimum and maximum values. * @return The descriptor for the given identifiers. */ protected static ParameterDescriptor<Double> createOptionalDescriptor( final ReferenceIdentifier[] identifiers, final double minimum, final double maximum, final Unit<?> unit) { return DefaultParameterDescriptor.create(toMap(identifiers), Double.NaN, minimum, maximum, unit, false); } /** * Constructs a parameter group from a set of alias. The parameter group is * identified by codes provided by one or more authorities. Common authorities are * {@link Citations#OGC OGC} and {@link Citations#EPSG EPSG} for example. * <P> * Special rules: * <ul> * <li>The first entry in the {@code identifiers} array is the * {@linkplain ParameterDescriptorGroup#getName primary name}.</li> * <li>If a an entry do not implements the {@link GenericName} interface, it is * an {@linkplain ParameterDescriptorGroup#getIdentifiers identifiers}.</li> * <li>All others are {@linkplain ParameterDescriptorGroup#getAlias aliases}.</li> * </ul> * * @param identifiers The operation identifiers. Most contains at least one entry. * @param parameters The set of parameters, or {@code null} or an empty array if none. * @return The descriptor for the given identifiers. */ protected static ParameterDescriptorGroup createDescriptorGroup( final ReferenceIdentifier[] identifiers, final GeneralParameterDescriptor[] parameters) { return new DefaultParameterDescriptorGroup(toMap(identifiers), parameters); } /** * Put the identifiers into a properties map suitable for {@link IdentifiedObject} * constructor. */ private static Map<String,Object> toMap(final ReferenceIdentifier[] identifiers) { ensureNonNull("identifiers", identifiers); if (identifiers.length == 0) { throw new IllegalArgumentException(Errors.format(ErrorKeys.EMPTY_ARRAY)); } int idCount = 0; int aliasCount = 0; ReferenceIdentifier[] id = new ReferenceIdentifier[identifiers.length]; GenericName[] alias = new GenericName [identifiers.length]; for (int i=0; i<identifiers.length; i++) { final ReferenceIdentifier candidate = identifiers[i]; if (candidate instanceof GenericName) { alias[aliasCount++] = (GenericName) candidate; } else { id[idCount++] = candidate; } } id = XArray.resize(id, idCount); alias = XArray.resize(alias, aliasCount); final Map<String,Object> properties = new HashMap<String,Object>(4, 0.8f); properties.put(NAME_KEY, identifiers[0]); properties.put(IDENTIFIERS_KEY, id); properties.put(ALIAS_KEY, alias); return properties; } /** * Ensures that the given set of parameters contains only valid values. * This method compares all parameter names against the names declared in the * {@linkplain #getParameters operation method parameter descriptor}. If an unknow * parameter name is found, then an {@link InvalidParameterNameException} is thrown. * This method also ensures that all values are assignable to the * {@linkplain ParameterDescriptor#getValueClass expected class}, are between the * {@linkplain ParameterDescriptor#getMinimumValue minimum} and * {@linkplain ParameterDescriptor#getMaximumValue maximum} values and are one of the * {@linkplain ParameterDescriptor#getValidValues set of valid values}. * If the value fails any of those tests, then an * {@link InvalidParameterValueException} is thrown. * * @param values The parameters values to check. * @return The parameter values to use for {@linkplain MathTransform math transform} * construction. May be different than the supplied {@code values} * argument if some missing values needed to be filled with default values. * @throws InvalidParameterNameException if a parameter name is unknow. * @throws InvalidParameterValueException if a parameter has an invalid value. */ protected ParameterValueGroup ensureValidValues(final ParameterValueGroup values) throws InvalidParameterNameException, InvalidParameterValueException { final ParameterDescriptorGroup parameters = getParameters(); final GeneralParameterDescriptor descriptor = values.getDescriptor(); if (parameters.equals(descriptor)) { /* * Since the "official" parameter descriptor was used, the descriptor should * have already enforced argument validity. Consequently, there is no need to * performs the check and we will avoid it as a performance enhancement. */ return values; } /* * Copy the all values from the user-supplied group to the provider-supplied group. * The provider group should performs all needed checks. Furthermore, it is suppliers * responsability to know about alias (e.g. OGC, EPSG, ESRI), while the user will * probably use the name from only one authority. With a copy, we gives a chances to * the provider-supplied parameters to uses its alias for understanding the user * parameter names. */ final ParameterValueGroup copy = parameters.createValue(); copy(values, copy); return copy; } /** * Implementation of {@code ensureValidValues}, to be invoked recursively * if the specified values contains sub-groups of values. This method copy all * values from the user-supplied parameter values into the provider-supplied * one. The provider one should understand alias, and performs name conversion * as well as argument checking on the fly. * * @param values The parameters values to copy. * @param copy The parameters values where to put the copy. * @throws InvalidParameterNameException if a parameter name is unknow. * @throws InvalidParameterValueException if a parameter has an invalid value. */ private static void copy(final ParameterValueGroup values, final ParameterValueGroup copy) throws InvalidParameterNameException, InvalidParameterValueException { for (final GeneralParameterValue value : values.values()) { final String name = value.getDescriptor().getName().getCode(); if (value instanceof ParameterValueGroup) { /* * Contains sub-group - invokes 'copy' recursively. */ final GeneralParameterDescriptor descriptor; descriptor = copy.getDescriptor().descriptor(name); if (descriptor instanceof ParameterDescriptorGroup) { final ParameterValueGroup groups = (ParameterValueGroup) descriptor.createValue(); copy((ParameterValueGroup) value, groups); values.groups(name).add(groups); continue; } else { throw new InvalidParameterNameException(Errors.format( ErrorKeys.UNEXPECTED_PARAMETER_$1, name), name); } } /* * Single parameter - copy the value, with special care for value with units. */ final ParameterValue<?> source = (ParameterValue) value; final ParameterValue<?> target; try { target = copy.parameter(name); } catch (ParameterNotFoundException cause) { final InvalidParameterNameException exception = new InvalidParameterNameException(Errors.format( ErrorKeys.UNEXPECTED_PARAMETER_$1, name), name); exception.initCause(cause); throw exception; } final Object v = source.getValue(); final Unit<?> unit = source.getUnit(); if (unit == null) { target.setValue(v); } else if (v instanceof Number) { target.setValue(((Number) v).doubleValue(), unit); } else if (v instanceof double[]) { target.setValue((double[]) v, unit); } else { throw new InvalidParameterValueException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, name, v), name, v); } } } /** * Returns the parameter value for the specified operation parameter. * This convenience method is used by subclasses for initializing * {@linkplain MathTransform math transform} from a set of parameters. * * @param param The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value. * @throws ParameterNotFoundException if the parameter is not found. */ protected static <T> ParameterValue<T> getParameter(final ParameterDescriptor<T> param, final ParameterValueGroup group) throws ParameterNotFoundException { /* * Search for an identifier matching the group's authority, if any. * This is needed if the parameter values group was created from an * EPSG database for example: we need to use the EPSG names instead * of the OGC ones. */ String name = getName(param, group.getDescriptor().getName().getAuthority()); if (name == null) { name = param.getName().getCode(); } if (param.getMinimumOccurs() != 0) { return Parameters.cast(group.parameter(name), param.getValueClass()); } /* * The parameter is optional. We don't want to invokes 'parameter(name)', because we don't * want to create a new parameter is the user didn't supplied one. Search the parameter * ourself (so we don't create any), and returns null if we don't find any. * * TODO: A simplier solution would be to add a 'isDefined' method in GeoAPI, * or something similar. */ final GeneralParameterDescriptor search; search = group.getDescriptor().descriptor(name); if (search instanceof ParameterDescriptor) { for (final GeneralParameterValue candidate : group.values()) { if (search.equals(candidate.getDescriptor())) { return Parameters.cast((ParameterValue) candidate, param.getValueClass()); } } } return null; } /** * Returns the parameter value for the specified operation parameter. * This convenience method is used by subclasses for initializing * {@linkplain MathTransform math transform} from a set of parameters. * * @param <T> The type of parameter value. * @param param The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if {@code param} is * {@linkplain #createOptionalDescriptor optional} and the user didn't * provided any value. * @throws ParameterNotFoundException if the parameter is not found. * * @todo Move to the {@link org.geotools.parameter.Parameters} class. */ protected static <T> T value(final ParameterDescriptor<T> param, final ParameterValueGroup group) throws ParameterNotFoundException { final ParameterValue<T> value = getParameter(param, group); return (value != null) ? value.getValue() : null; } /** * Returns the parameter value for the specified operation parameter. * This convenience method is used by subclasses for initializing * {@linkplain MathTransform math transform} from a set of parameters. * * @param param The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if {@code param} is * {@linkplain #createOptionalDescriptor optional} and the user didn't * provided any value. * @throws ParameterNotFoundException if the parameter is not found. * * @todo Move to the {@link org.geotools.parameter.Parameters} class. */ protected static String stringValue(final ParameterDescriptor<?> param, final ParameterValueGroup group) throws ParameterNotFoundException { final ParameterValue<?> value = getParameter(param, group); return (value != null) ? value.stringValue() : null; } /** * Returns the parameter value for the specified operation parameter. * This convenience method is used by subclasses for initializing * {@linkplain MathTransform math transform} from a set of parameters. * * @param param The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code 0} if {@code param} is * {@linkplain #createOptionalDescriptor optional} and the user didn't * provided any value. * @throws ParameterNotFoundException if the parameter is not found. * * @todo Move to the {@link org.geotools.parameter.Parameters} class. */ protected static int intValue(final ParameterDescriptor<?> param, final ParameterValueGroup group) throws ParameterNotFoundException { final ParameterValue<?> value = getParameter(param, group); return (value != null) ? value.intValue() : 0; } /** * Returns the parameter value for the specified operation parameter. * Values are automatically converted into the standard units specified * by the supplied {@code param} argument. * This convenience method is used by subclasses for initializing * {@linkplain MathTransform math transform} from a set of parameters. * * @param param The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code NaN} if {@code param} is * {@linkplain #createOptionalDescriptor optional} and the user didn't * provided any value. * @throws ParameterNotFoundException if the parameter is not found. * * @todo Move to the {@link org.geotools.parameter.Parameters} class. */ protected static double doubleValue(final ParameterDescriptor<?> param, final ParameterValueGroup group) throws ParameterNotFoundException { final Unit<?> unit = param.getUnit(); final ParameterValue<?> value = getParameter(param, group); return (value == null) ? Double.NaN : (unit != null) ? value.doubleValue(unit) : value.doubleValue(); } /** * Format the inner part of a * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well * Known Text</cite> (WKT)</A> element. * * @param formatter The formatter to use. * @return The WKT element name. */ @Override protected String formatWKT(final Formatter formatter) { final Class type = getOperationType(); if (Projection.class.isAssignableFrom(type)) { return super.formatWKT(formatter); } formatter.setInvalidWKT(OperationMethod.class); return "OperationMethod"; } /** * The result of a call to {@link MathTransformProvider#createMathTransform createMathTransform}. * This class encapsulates a reference to the {@linkplain #method originating provider} * as well as the {@linkplain #transform created math transform}. This information is needed * when a provider delegates the work to an other provider according the parameter values. * For example a generic instance of * {@link org.geotools.referencing.operation.transform.ProjectiveTransform.ProviderAffine * ProviderAffine} may delegates the creation of an <cite>affine transform</cite> to an other * {@code ProviderAffine} instance with <cite>source</cite> and <cite>target</cite> dimensions * matching the supplied parameters, because those dimensions determine the set of legal * <code>"elt_<var>j</var>_<var>i</var>"</code> parameters. * <p> * Most {@linkplain MathTransformProvider math transform provider} do not delegate their work * to an other one, and consequently do not need this class. * <p> * Future Geotools version may extends this class for handling more information than just the * {@linkplain #transform transform} creator. This class is more convenient than adding new * methods right into {@link MathTransformProvider}, because it is sometime difficult for a * provider to infer all the conditions prevaling when * {@link MathTransformProvider#createMathTransform createMathTransform} was executed. * Furthermore, it avoid to pollute {@code MathTransformProvider} with methods unused * for the vast majority of providers. * * @version $Id$ * @author Martin Desruisseaux (IRD) * * @since 2.2 */ protected static final class Delegate extends MathTransformProxy { /** * For cross-version compatibility. */ private static final long serialVersionUID = -3942740060970730790L; /** * The provider for the {@linkplain #transform transform}. */ public final OperationMethod method; /** * Encapsulates the math transform created by the specified provider. * * @param transform The math transform created by provider. * @param method The provider, typically as an instance of {@link MathTransformProvider}. */ public Delegate(final MathTransform transform, final OperationMethod method) { super(transform); this.method = method; ensureNonNull("transform", transform); ensureNonNull("method", method); } } }