blob: c114d58716d5fcd9857b1bcf3faf58fee8aca4a8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing.operation;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import javax.xml.bind.annotation.XmlTransient;
import javax.measure.unit.Unit;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.quality.PositionalAccuracy;
import org.opengis.util.InternationalString;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.referencing.operation.SingleOperation;
import org.apache.sis.internal.metadata.ReferencingServices;
import org.apache.sis.internal.referencing.SignReversalComment;
import org.apache.sis.internal.referencing.provider.AbstractProvider;
import org.apache.sis.metadata.iso.ImmutableIdentifier;
import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.Deprecable;
/**
* Description of the inverse of another method. This class should be used only when no operation is defined
* for the inverse, or when the inverse operation can not be represented by inverting the sign of parameters.
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.7
* @version 0.8
* @module
*/
@XmlTransient
final class InverseOperationMethod extends DefaultOperationMethod {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 6395008927817202180L;
/**
* The original operation method for which this {@code InverseOperationMethod} is the inverse.
*/
private final OperationMethod inverse;
/**
* Creates the inverse of the given method.
*/
private InverseOperationMethod(final Map<String,?> properties, final OperationMethod method) {
super(properties, method.getTargetDimensions(), method.getSourceDimensions(), method.getParameters());
inverse = method;
}
/**
* Returns {@code true} if the given method flags itself as invertible.
*/
private static boolean isInvertible(final OperationMethod method) {
return method instanceof AbstractProvider && ((AbstractProvider) method).isInvertible();
}
/**
* Returns or create the inverse of the given operation method. If the same operation method can be used
* for the inverse operation either with the exact same parameter values or with the sign of some values
* reversed, then the given method is returned as-is. Otherwise a synthetic method is created.
*/
static OperationMethod create(final OperationMethod method) {
if (method instanceof InverseOperationMethod) {
return ((InverseOperationMethod) method).inverse;
}
if (!isInvertible(method)) {
boolean useSameParameters = false;
for (final GeneralParameterDescriptor descriptor : method.getParameters().descriptors()) {
useSameParameters = (descriptor.getRemarks() instanceof SignReversalComment);
if (!useSameParameters) break;
}
if (!useSameParameters) {
Identifier name = method.getName();
name = new ImmutableIdentifier(null, null, "Inverse of " + name.getCode());
final Map<String,Object> properties = new HashMap<>(6);
properties.put(NAME_KEY, name);
properties.put(FORMULA_KEY, method.getFormula());
properties.put(REMARKS_KEY, method.getRemarks());
if (method instanceof Deprecable) {
properties.put(DEPRECATED_KEY, ((Deprecable) method).isDeprecated());
}
return new InverseOperationMethod(properties, method);
}
}
return method;
}
/**
* Infers the properties to give to an inverse coordinate operation.
* The returned map will contains three kind of information:
*
* <ul>
* <li>Metadata (domain of validity, accuracy)</li>
* <li>Parameter values, if possible</li>
* </ul>
*
* This method copies accuracy and domain of validity metadata from the given operation.
* We presume that the inverse operation has the same accuracy than the direct operation.
*
* <div class="note"><b>Note:</b>
* in many cases, the inverse operation is numerically less accurate than the direct operation because it
* uses approximations like series expansions or iterative methods. However the numerical errors caused by
* those approximations are not of interest here, because they are usually much smaller than the inaccuracy
* due to the stochastic nature of coordinate transformations (not to be confused with coordinate conversions;
* see ISO 19111 for more information).</div>
*
* If the inverse of the given operation can be represented by inverting the sign of all numerical
* parameter values, then this method copies also those parameters in a {@code "parameters"} entry.
*
* @param source the operation for which to get the inverse parameters.
* @param target where to store the inverse parameters.
*/
static void properties(final SingleOperation source, final Map<String,Object> target) {
target.put(SingleOperation.DOMAIN_OF_VALIDITY_KEY, source.getDomainOfValidity());
final Collection<PositionalAccuracy> accuracy = source.getCoordinateOperationAccuracy();
if (!Containers.isNullOrEmpty(accuracy)) {
target.put(SingleOperation.COORDINATE_OPERATION_ACCURACY_KEY,
accuracy.toArray(new PositionalAccuracy[accuracy.size()]));
}
/*
* If the inverse of the given operation can be represented by inverting the sign of all numerical
* parameter values, copies those parameters in a "parameters" entry in the properties map.
* Otherwise does nothing.
*/
final boolean isInvertible = isInvertible(source.getMethod());
final ParameterValueGroup parameters = source.getParameterValues();
final ParameterValueGroup copy = parameters.getDescriptor().createValue();
for (final GeneralParameterValue gp : parameters.values()) {
if (gp instanceof ParameterValue<?>) {
final ParameterValue<?> src = (ParameterValue<?>) gp;
final Object value = src.getValue();
if (value instanceof Number) {
final ParameterDescriptor<?> descriptor = src.getDescriptor();
final InternationalString remarks = descriptor.getRemarks();
if (remarks != SignReversalComment.SAME) {
boolean isOpposite = (remarks == SignReversalComment.OPPOSITE);
if (!isOpposite) {
/*
* If the parameter descriptor does not contain an information about whether the
* inverse operation uses values of opposite sign or not, use heuristic rules.
*/
if (!isInvertible) {
return; // Can not create inverse parameter values - abandon.
}
final Comparable<?> minimum = descriptor.getMinimumValue();
isOpposite = (minimum == null || (minimum instanceof Number && ((Number) minimum).doubleValue() < 0));
}
if (isOpposite) {
final ParameterValue<?> tgt = copy.parameter(descriptor.getName().getCode());
final Unit<?> unit = src.getUnit();
if (unit != null) {
tgt.setValue(-src.doubleValue(), unit);
} else if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
tgt.setValue(-src.intValue());
} else {
tgt.setValue(-src.doubleValue());
}
continue;
}
}
}
}
copy.values().add(gp);
}
target.put(ReferencingServices.PARAMETERS_KEY, copy);
}
}