| /* |
| * 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.List; |
| import java.util.Objects; |
| import jakarta.xml.bind.Unmarshaller; |
| import jakarta.xml.bind.annotation.XmlType; |
| import jakarta.xml.bind.annotation.XmlElement; |
| import jakarta.xml.bind.annotation.XmlRootElement; |
| import org.opengis.util.InternationalString; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.operation.Formula; |
| import org.opengis.referencing.operation.Conversion; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.OperationMethod; |
| import org.opengis.referencing.operation.SingleOperation; |
| import org.opengis.parameter.GeneralParameterDescriptor; |
| import org.opengis.parameter.ParameterDescriptorGroup; |
| import org.opengis.parameter.ParameterDescriptor; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.Utilities; |
| import org.apache.sis.util.Workaround; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.SimpleInternationalString; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.resources.Vocabulary; |
| import org.apache.sis.referencing.NamedIdentifier; |
| import org.apache.sis.referencing.IdentifiedObjects; |
| import org.apache.sis.referencing.AbstractIdentifiedObject; |
| import org.apache.sis.referencing.privy.WKTKeywords; |
| import org.apache.sis.referencing.privy.NilReferencingObject; |
| import org.apache.sis.xml.bind.gco.StringAdapter; |
| import org.apache.sis.xml.bind.referencing.CC_OperationMethod; |
| import org.apache.sis.metadata.privy.Identifiers; |
| import org.apache.sis.metadata.privy.ImplementationHelper; |
| import org.apache.sis.parameter.DefaultParameterDescriptorGroup; |
| import org.apache.sis.parameter.Parameterized; |
| import org.apache.sis.io.wkt.Formatter; |
| import org.apache.sis.io.wkt.ElementKind; |
| import org.apache.sis.io.wkt.FormattableObject; |
| |
| // Specific to the geoapi-4.0 branch: |
| import org.opengis.referencing.crs.DerivedCRS; |
| |
| |
| /** |
| * Describes the algorithm and parameters used to perform a coordinate operation. An {@code OperationMethod} |
| * is a kind of metadata: it does not perform any coordinate operation (e.g. map projection) by itself, but |
| * tells us what is needed in order to perform such operation. |
| * |
| * <p>The most important parts of an {@code OperationMethod} are its {@linkplain #getName() name} and its |
| * {@linkplain #getParameters() group of parameter descriptors}. The parameter descriptors do not contain |
| * any value, but tell us what are the expected parameters, together with their units of measurement.</p> |
| * |
| * <p>In Apache SIS implementation, the {@linkplain #getName() name} is the only mandatory property. |
| * However, it is recommended to provide also {@linkplain #getIdentifiers() identifiers} |
| * (e.g. “EPSG:9804” in the following example) |
| * because names can sometimes be ambiguous or be spelled in different ways.</p> |
| * |
| * <h2>Example</h2> |
| * An operation method named <q>Mercator (variant A)</q> (EPSG:9804) expects the following parameters: |
| * <ul> |
| * <li><q>Latitude of natural origin</q> in degrees. Default value is 0°.</li> |
| * <li><q>Longitude of natural origin</q> in degrees. Default value is 0°.</li> |
| * <li><q>Scale factor at natural origin</q> as a dimensionless number. Default value is 1.</li> |
| * <li><q>False easting</q> in metres. Default value is 0 m.</li> |
| * <li><q>False northing</q> in metres. Default value is 0 m.</li> |
| * </ul> |
| * |
| * <h2>Departure from the ISO 19111 standard</h2> |
| * The following properties are mandatory according ISO 19111, |
| * but may be missing under some conditions in Apache SIS: |
| * <ul> |
| * <li>The {@linkplain #getFormula() formula} if it has not been provided to the |
| * {@linkplain #DefaultOperationMethod(Map, ParameterDescriptorGroup) constructor}, or if it |
| * cannot be {@linkplain #DefaultOperationMethod(MathTransform) inferred from the given math transform}.</li> |
| * <li>The {@linkplain #getParameters() parameters} if the {@link #DefaultOperationMethod(MathTransform)} |
| * constructor cannot infer them.</li> |
| * </ul> |
| * |
| * <h2>Relationship with other classes or interfaces</h2> |
| * {@code OperationMethod} describes parameters without providing any value (except sometimes default values). |
| * When values have been assigned to parameters, the result is a {@link SingleOperation}. |
| * Note that there is different kinds of {@code SingleOperation} depending on the nature and accuracy of the |
| * coordinate operation. See {@link #getOperationType()} for more information. |
| * |
| * <p>The interface performing the actual work of taking coordinates in the |
| * {@linkplain AbstractCoordinateOperation#getSourceCRS() source CRS} and calculating the new coordinates in the |
| * {@linkplain AbstractCoordinateOperation#getTargetCRS() target CRS} is {@link MathTransform}. |
| * In order to allow Apache SIS to instantiate those {@code MathTransform}s from given parameter values, |
| * {@code DefaultOperationMethod} subclasses should implement the |
| * {@link org.apache.sis.referencing.operation.transform.MathTransformProvider} interface.</p> |
| * |
| * <h2>Immutability and thread safety</h2> |
| * This class is immutable and thread-safe if all properties given to the constructor are also immutable and thread-safe. |
| * It is strongly recommended for all subclasses to be thread-safe, especially the |
| * {@link org.apache.sis.referencing.operation.transform.MathTransformProvider} implementations to be used with |
| * {@link org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory}. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.5 |
| * |
| * @see DefaultConversion |
| * @see DefaultTransformation |
| * @see org.apache.sis.referencing.operation.transform.MathTransformProvider |
| * |
| * @since 0.5 |
| */ |
| @XmlType(name = "OperationMethodType", propOrder = { |
| "formulaCitation", |
| "formulaDescription", |
| "descriptors" |
| }) |
| @XmlRootElement(name = "OperationMethod") |
| public class DefaultOperationMethod extends AbstractIdentifiedObject implements OperationMethod { |
| /* |
| * NOTE FOR JAVADOC WRITER: |
| * The "method" word is ambiguous here, because it can be "Java method" or "coordinate operation method". |
| * In this class, we reserve the "method" word for "coordinate operation method" as much as possible. |
| */ |
| |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = 6612049971779439502L; |
| |
| /** |
| * Formula(s) or procedure used by this operation method. This may be a reference to a publication. |
| * Note that the operation method may not be analytic, in which case this attribute references or |
| * contains the procedure, not an analytic formula. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is modified only at unmarshalling time by {@link #setFormulaCitation(Citation)} |
| * or {@link #setFormulaDescription(String)}.</p> |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| private Formula formula; |
| |
| /** |
| * The set of parameters, or {@code null} if none. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is modified only at unmarshalling time by {@link #setDescriptors(GeneralParameterDescriptor[])} |
| * or {@link #afterUnmarshal(Unmarshaller, Object)}.</p> |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| private ParameterDescriptorGroup parameters; |
| |
| /** |
| * Constructs an operation method from a set of properties and a descriptor group. The properties map is given |
| * unchanged to the {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}. |
| * In addition to the properties documented in the parent constructor, |
| * the following properties are understood by this constructor: |
| * |
| * <table class="sis"> |
| * <caption>Recognized properties (non exhaustive list)</caption> |
| * <tr> |
| * <th>Property name</th> |
| * <th>Value type</th> |
| * <th>Returned by</th> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.operation.OperationMethod#FORMULA_KEY}</td> |
| * <td>{@link Formula}, {@link Citation} or {@link CharSequence}</td> |
| * <td>{@link #getFormula()}</td> |
| * </tr><tr> |
| * <th colspan="3" class="hsep">Defined in parent classes (reminder)</th> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td> |
| * <td>{@link org.opengis.metadata.Identifier} or {@link String}</td> |
| * <td>{@link #getName()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td> |
| * <td>{@link org.opengis.util.GenericName} or {@link CharSequence} (optionally as array)</td> |
| * <td>{@link #getAlias()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td> |
| * <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td> |
| * <td>{@link #getIdentifiers()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link #getRemarks()}</td> |
| * </tr> |
| * </table> |
| * |
| * @param properties set of properties. Shall contain at least {@code "name"}. |
| * @param parameters description of parameters expected by this operation. |
| * |
| * @since 1.1 |
| */ |
| public DefaultOperationMethod(final Map<String,?> properties, |
| final ParameterDescriptorGroup parameters) |
| { |
| super(properties); |
| ArgumentChecks.ensureNonNull("parameters", parameters); |
| Object value = properties.get(FORMULA_KEY); |
| if (value == null || value instanceof Formula) { |
| formula = (Formula) value; |
| } else if (value instanceof Citation) { |
| formula = new DefaultFormula((Citation) value); |
| } else if (value instanceof CharSequence) { |
| formula = new DefaultFormula((CharSequence) value); |
| } else { |
| throw new IllegalArgumentException(Errors.forProperties(properties) |
| .getString(Errors.Keys.IllegalPropertyValueClass_2, FORMULA_KEY, value.getClass())); |
| } |
| this.parameters = parameters; |
| } |
| |
| /** |
| * Convenience constructor that creates an operation method from a math transform. |
| * The information provided in the newly created object are approximations, and |
| * usually acceptable only as a fallback when no other information are available. |
| * |
| * @param transform the math transform to describe. |
| */ |
| public DefaultOperationMethod(final MathTransform transform) { |
| super(getProperties(transform)); |
| if (transform instanceof Parameterized) { |
| parameters = ((Parameterized) transform).getParameterDescriptors(); |
| } |
| } |
| |
| /** |
| * Work around for RFE #4093999 in Sun's bug database |
| * ("Relax constraint on placement of this()/super() call in constructors"). |
| */ |
| @Workaround(library="JDK", version="1.7") |
| private static Map<String,?> getProperties(final MathTransform transform) { |
| ArgumentChecks.ensureNonNull("transform", transform); |
| if (transform instanceof Parameterized) { |
| final ParameterDescriptorGroup parameters = ((Parameterized) transform).getParameterDescriptors(); |
| if (parameters != null) { |
| return getProperties(parameters, null); |
| } |
| } |
| return Map.of(NAME_KEY, NilReferencingObject.UNNAMED); |
| } |
| |
| /** |
| * Returns the properties to be given to an identified object derived from the specified one. |
| * This method returns the same properties as the supplied argument |
| * (as of <code>{@linkplain IdentifiedObjects#getProperties getProperties}(info)</code>), |
| * except for the following: |
| * |
| * <ul> |
| * <li>The {@linkplain IdentifiedObject#getName() name}'s authority is replaced by the specified one.</li> |
| * <li>All {@linkplain IdentifiedObject#getIdentifiers identifiers} are removed, because the new object |
| * to be created is probably not endorsed by the original authority.</li> |
| * </ul> |
| * |
| * This method returns a mutable map. Consequently, callers can add their own identifiers |
| * directly to this map if they wish. |
| * |
| * @param info the identified object to view as a properties map. |
| * @param authority the new authority for the object to be created, |
| * or {@code null} if it is not going to have any declared authority. |
| * @return the identified object properties in a mutable map. |
| */ |
| private static Map<String,Object> getProperties(final IdentifiedObject info, final Citation authority) { |
| final Map<String,Object> properties = new HashMap<>(IdentifiedObjects.getProperties(info)); |
| properties.put(NAME_KEY, new NamedIdentifier(authority, info.getName().getCode())); |
| properties.remove(IDENTIFIERS_KEY); |
| return properties; |
| } |
| |
| /** |
| * Creates a new operation method with the same values as the specified one. |
| * This copy constructor provides a way to convert an arbitrary implementation into a SIS one |
| * or a user-defined one (as a subclass), usually in order to leverage some implementation-specific API. |
| * |
| * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p> |
| * |
| * @param method the operation method to copy. |
| * |
| * @see #castOrCopy(OperationMethod) |
| */ |
| protected DefaultOperationMethod(final OperationMethod method) { |
| super(method); |
| formula = method.getFormula(); |
| parameters = method.getParameters(); |
| } |
| |
| /** |
| * Returns a SIS operation method implementation with the same values as the given arbitrary implementation. |
| * If the given object is {@code null}, then {@code null} is returned. |
| * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged. |
| * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object. |
| * |
| * @param object the object to get as a SIS implementation, or {@code null} if none. |
| * @return a SIS implementation containing the values of the given object (may be the |
| * given object itself), or {@code null} if the argument was null. |
| */ |
| public static DefaultOperationMethod castOrCopy(final OperationMethod object) { |
| return (object == null) || (object instanceof DefaultOperationMethod) |
| ? (DefaultOperationMethod) object : new DefaultOperationMethod(object); |
| } |
| |
| /** |
| * Returns the GeoAPI interface implemented by this class. |
| * The SIS implementation returns {@code OperationMethod.class}. |
| * |
| * <h4>Note for implementers</h4> |
| * Subclasses usually do not need to override this information since GeoAPI does not define {@code OperationMethod} |
| * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their |
| * own set of interfaces. |
| * |
| * @return {@code OperationMethod.class} or a user-defined sub-interface. |
| */ |
| @Override |
| public Class<? extends OperationMethod> getInterface() { |
| return OperationMethod.class; |
| } |
| |
| /** |
| * Returns the base interface of the {@code CoordinateOperation} instances that use this method. |
| * The base {@code CoordinateOperation} interface is usually one of the following subtypes: |
| * |
| * <ul class="verbose"> |
| * <li class="verbose">{@link org.opengis.referencing.operation.Conversion} |
| * if the coordinate operation is theoretically of infinite precision, ignoring the limitations of floating |
| * point arithmetic (including rounding errors) and the approximations implied by finite series expansions.</li> |
| * <li>{@link org.opengis.referencing.operation.Transformation} |
| * if the coordinate operation has some errors (typically of a few metres) because of the empirical process by |
| * which the operation parameters were determined. Those errors do not depend on the floating point precision |
| * or the accuracy of the implementation algorithm.</li> |
| * <li>{@link org.opengis.referencing.operation.PointMotionOperation} |
| * if the coordinate operation applies changes due to the motion of points between two coordinate epochs.</li> |
| * </ul> |
| * |
| * In case of doubt, {@code getOperationType()} can conservatively return the base type. |
| * The default implementation returns {@code SingleOperation.class}, |
| * which is the most conservative return value. |
| * |
| * @return interface implemented by all coordinate operations that use this method. |
| * |
| * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getAvailableMethods(Class) |
| */ |
| public Class<? extends SingleOperation> getOperationType() { |
| return SingleOperation.class; |
| } |
| |
| /** |
| * Formula(s) or procedure used by this operation method. This may be a reference to a |
| * publication. Note that the operation method may not be analytic, in which case this |
| * attribute references or contains the procedure, not an analytic formula. |
| * |
| * <h4>Departure from the ISO 19111 standard</h4> |
| * This property is mandatory according ISO 19111, but optional in Apache SIS. |
| * |
| * @return the formula used by this method, or {@code null} if unknown. |
| * |
| * @see DefaultFormula |
| * @see org.apache.sis.referencing.operation.transform.MathTransformProvider |
| */ |
| @Override |
| public Formula getFormula() { |
| return formula; |
| } |
| |
| /** |
| * Returns the set of parameters. |
| * |
| * <h4>Departure from the ISO 19111 standard</h4> |
| * This property is mandatory according ISO 19111, but may be {@code null} in Apache SIS if the |
| * {@link #DefaultOperationMethod(MathTransform)} constructor has been unable to infer it. |
| * |
| * @return the parameters, or {@code null} if unknown. |
| * |
| * @see DefaultConversion#getParameterDescriptors() |
| * @see DefaultConversion#getParameterValues() |
| */ |
| @Override |
| public ParameterDescriptorGroup getParameters() { |
| return parameters; |
| } |
| |
| /** |
| * Compares this operation method with the specified object for equality. |
| * If the {@code mode} argument value is {@link ComparisonMode#STRICT STRICT} or |
| * {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available properties |
| * are compared including the {@linkplain #getFormula() formula}. |
| * |
| * @param object the object to compare to {@code this}. |
| * @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or |
| * {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only |
| * properties relevant to transformations. |
| * @return {@code true} if both objects are equal. |
| */ |
| @Override |
| public boolean equals(final Object object, final ComparisonMode mode) { |
| if (object == this) { |
| return true; // Slight optimization. |
| } |
| if (super.equals(object, mode)) { |
| switch (mode) { |
| case STRICT: { |
| // Name and identifiers have been compared by super.equals(object, mode). |
| final DefaultOperationMethod that = (DefaultOperationMethod) object; |
| return Objects.equals(this.formula, that.formula) && |
| Objects.equals(this.parameters, that.parameters); |
| } |
| case BY_CONTRACT: { |
| // Name and identifiers have been compared by super.equals(object, mode). |
| if (!Objects.equals(getFormula(), ((OperationMethod) object).getFormula())) { |
| return false; |
| } |
| break; |
| } |
| default: { |
| /* |
| * Name and identifiers have been ignored by super.equals(object, mode). |
| * Since they are significant for OperationMethod, we compare them here. |
| * |
| * According ISO 19162 (Well Known Text representation of Coordinate Reference Systems), |
| * identifiers shall have precedence over name at least in the case of operation methods |
| * and parameters. |
| */ |
| final OperationMethod that = (OperationMethod) object; |
| final Boolean match = Identifiers.hasCommonIdentifier(getIdentifiers(), that.getIdentifiers()); |
| if (match != null) { |
| if (!match) { |
| return false; |
| } |
| } else if (!isHeuristicMatchForName(that.getName().getCode()) |
| && !IdentifiedObjects.isHeuristicMatchForName(that, getName().getCode())) |
| { |
| return false; |
| } |
| break; |
| } |
| } |
| final OperationMethod that = (OperationMethod) object; |
| return Utilities.deepEquals(getParameters(), that.getParameters(), mode); |
| } |
| return false; |
| } |
| |
| /** |
| * Invoked by {@code hashCode()} for computing the hash code when first needed. |
| * See {@link org.apache.sis.referencing.AbstractIdentifiedObject#computeHashCode()} |
| * for more information. |
| * |
| * @return the hash code value. This value may change in any future Apache SIS version. |
| */ |
| @Override |
| protected long computeHashCode() { |
| return super.computeHashCode() + Objects.hashCode(parameters); |
| } |
| |
| /** |
| * Formats this operation as a <i>Well Known Text</i> {@code Method[…]} element. |
| * |
| * @return {@code "Method"} (WKT 2) or {@code "Projection"} (WKT 1). |
| * |
| * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#118">WKT 2 specification §17.2.3</a> |
| */ |
| @Override |
| protected String formatTo(final Formatter formatter) { |
| final boolean isWKT1 = formatter.getConvention().majorVersion() == 1; |
| /* |
| * The next few lines below are basically a copy of the work done by super.formatTo(formatter), |
| * which search for the name to write inside METHOD["name"]. The difference is in the fallback |
| * executed if we do not find a name for the given authority. |
| */ |
| final Citation authority = formatter.getNameAuthority(); |
| String name = IdentifiedObjects.getName(this, authority); |
| ElementKind kind = ElementKind.METHOD; |
| if (name == null) { |
| /* |
| * No name found for the given authority. We may use the primary name as a fallback. |
| * But before doing that, maybe we can find the name that we are looking for in the |
| * hard-coded values in the 'org.apache.sis.referencing.operation.provider' package. |
| * The typical use case is when this DefaultOperationMethod has been instantiated |
| * by the EPSG factory using only the information found in the EPSG database. |
| * |
| * We can find the hard-coded names by looking at the ParameterDescriptorGroup of the |
| * enclosing ProjectedCRS or DerivedCRS. This is because that parameter descriptor was |
| * typically provided by the 'org.apache.sis.referencing.operation.provider' package in |
| * order to create the MathTransform associated with the enclosing CRS. The enclosing |
| * CRS is either the immediate parent in WKT 1, or the parent of the parent in WKT 2. |
| */ |
| final FormattableObject parent = formatter.getEnclosingElement(isWKT1 ? 1 : 2); |
| if (parent instanceof DerivedCRS) { |
| final Conversion conversion = ((DerivedCRS) parent).getConversionFromBase(); |
| if (conversion != null) { // Should never be null, but let be safe. |
| final ParameterDescriptorGroup descriptor; |
| if (conversion instanceof Parameterized) { // Usual case in SIS implementation. |
| descriptor = ((Parameterized) conversion).getParameterDescriptors(); |
| } else { |
| descriptor = conversion.getParameterValues().getDescriptor(); |
| } |
| name = IdentifiedObjects.getName(descriptor, authority); |
| } |
| } |
| if (name == null) { |
| name = IdentifiedObjects.getName(this, null); |
| if (name == null) { |
| name = Vocabulary.forLocale(formatter.getLocale()).getString(Vocabulary.Keys.Unnamed); |
| kind = ElementKind.NAME; // Because the "Unnamed" string is not a real OperationMethod name. |
| } |
| } |
| } |
| formatter.append(name, kind); |
| if (isWKT1) { |
| /* |
| * The WKT 1 keyword is "PROJECTION", which imply that the operation method should be of type |
| * org.opengis.referencing.operation.Conversion. So strictly speaking only the first check in |
| * the following 'if' statement is relevant, and we should also check CRS types. |
| * |
| * Unfortunately in many cases we do not know the operation type, because the method that we |
| * invoked - getOperationType() - is not a standard OGC/ISO property, so this information is |
| * usually not provided in XML documents for example. The user could also have instantiated |
| * DirectOperationMethod directly without creating a subclass. Consequently, we also accept to |
| * format the keyword as "PROJECTION" if the operation type *could* be a projection. This is |
| * the second check in the following 'if' statement. |
| * |
| * In other words, the combination of those two checks exclude the following operation types: |
| * Transformation, ConcatenatedOperation, PassThroughOperation, or any user-defined type that |
| * do not extend Conversion. All other operation types are accepted. |
| */ |
| final Class<? extends SingleOperation> type = getOperationType(); |
| if (Conversion.class.isAssignableFrom(type) || type.isAssignableFrom(Conversion.class)) { |
| return WKTKeywords.Projection; |
| } |
| formatter.setInvalidWKT(this, null); |
| } |
| return WKTKeywords.Method; |
| } |
| |
| |
| |
| |
| /* |
| ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ |
| ┃ ┃ |
| ┃ XML support with JAXB ┃ |
| ┃ ┃ |
| ┃ The following methods are invoked by JAXB using reflection (even if ┃ |
| ┃ they are private) or are helpers for other methods invoked by JAXB. ┃ |
| ┃ Those methods can be safely removed if Geographic Markup Language ┃ |
| ┃ (GML) support is not needed. ┃ |
| ┃ ┃ |
| ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ |
| */ |
| |
| /** |
| * Creates a new object in which every attributes are set to a null value. |
| * <strong>This is not a valid object.</strong> This constructor is strictly |
| * reserved to JAXB, which will assign values to the fields using reflection. |
| */ |
| private DefaultOperationMethod() { |
| super(org.apache.sis.referencing.privy.NilReferencingObject.INSTANCE); |
| } |
| |
| /** |
| * Invoked by JAXB for marshalling a citation to the formula. In principle at most one of |
| * {@code getFormulaCitation()} and {@link #getFormulaDescription()} methods can return a |
| * non-null value. However, SIS accepts both coexist (but this is invalid GML). |
| */ |
| @XmlElement(name = "formulaCitation") |
| private Citation getFormulaCitation() { |
| final Formula formula = getFormula(); // Give to users a chance to override. |
| return (formula != null) ? formula.getCitation() : null; |
| } |
| |
| /** |
| * Invoked by JAXB for marshalling the formula literally. In principle at most one of |
| * {@code getFormulaDescription()} and {@link #getFormulaCitation()} methods can return |
| * a non-null value. However, SIS accepts both to coexist (but this is invalid GML). |
| */ |
| @XmlElement(name = "formula") |
| private String getFormulaDescription() { |
| final Formula formula = getFormula(); // Give to users a chance to override. |
| return (formula != null) ? StringAdapter.toString(formula.getFormula()) : null; |
| } |
| |
| /** |
| * Invoked by JAXB for setting the citation to the formula. |
| */ |
| private void setFormulaCitation(final Citation citation) { |
| if (formula == null || formula.getCitation() == null) { |
| formula = (formula == null) ? new DefaultFormula(citation) |
| : new DefaultFormula(formula.getFormula(), citation); |
| } else { |
| ImplementationHelper.propertyAlreadySet(DefaultOperationMethod.class, "setFormulaCitation", "formulaCitation"); |
| } |
| } |
| |
| /** |
| * Invoked by JAXB for setting the formula description. |
| */ |
| private void setFormulaDescription(final String description) { |
| if (formula == null || formula.getFormula() == null) { |
| formula = (formula == null) ? new DefaultFormula(description) |
| : new DefaultFormula(new SimpleInternationalString(description), formula.getCitation()); |
| } else { |
| ImplementationHelper.propertyAlreadySet(DefaultOperationMethod.class, "setFormulaDescription", "formula"); |
| } |
| } |
| |
| /** |
| * Invoked by JAXB for getting the parameters to marshal. This method usually marshals the sequence of |
| * descriptors without their {@link ParameterDescriptorGroup} wrapper, because GML is defined that way. |
| * The {@code ParameterDescriptorGroup} wrapper is a GeoAPI addition done for allowing usage of its |
| * methods as a convenience (e.g. {@link ParameterDescriptorGroup#descriptor(String)}). |
| * |
| * <p>However, it could happen that the user really wanted to specify a {@code ParameterDescriptorGroup} as |
| * the sole {@code <gml:parameter>} element. We currently have no easy way to distinguish those cases.</p> |
| * |
| * <h4>Tip</h4> |
| * One possible way to distinguish the two cases would be to check that the parameter group does not contain |
| * any property that this method does not have: |
| * |
| * {@snippet lang="java" : |
| * if (IdentifiedObjects.getProperties(this).entrySet().containsAll( |
| * IdentifiedObjects.getProperties(parameters).entrySet())) ... |
| * } |
| * |
| * But we would need to make sure that {@link AbstractSingleOperation#getParameters()} is consistent |
| * with the decision taken by this method. |
| * |
| * <h4>Historical note</h4> |
| * Older, deprecated, names for the parameters were: |
| * <ul> |
| * <li>{@code includesParameter}</li> |
| * <li>{@code generalOperationParameter} - note that this name was used by the EPSG repository</li> |
| * <li>{@code usesParameter}</li> |
| * </ul> |
| * |
| * @see #getParameters() |
| * @see AbstractSingleOperation#getParameters() |
| */ |
| @XmlElement(name = "parameter") |
| private GeneralParameterDescriptor[] getDescriptors() { |
| if (parameters != null) { |
| final List<GeneralParameterDescriptor> descriptors = parameters.descriptors(); |
| if (descriptors != null) { // Paranoiac check (should not be allowed). |
| return CC_OperationMethod.filterImplicit(descriptors.toArray(GeneralParameterDescriptor[]::new)); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Invoked by JAXB for setting the unmarshalled parameters. |
| * This method wraps the given descriptors in a {@link DefaultParameterDescriptorGroup}. |
| * |
| * <p>The parameter descriptors created by this method are incomplete since we cannot |
| * provide a non-null value for {@link ParameterDescriptor#getValueClass()}. The value |
| * class will be provided either by replacing this {@code OperationMethod} by one of the |
| * predefined methods, or by unmarshalling the enclosing {@link AbstractSingleOperation}.</p> |
| * |
| * <p><b>Maintenance note:</b> the {@code "setDescriptors"} method name is also hard-coded in |
| * {@link org.apache.sis.xml.bind.referencing.CC_GeneralOperationParameter} for logging purpose.</p> |
| * |
| * @see AbstractSingleOperation#setParameters |
| */ |
| private void setDescriptors(final GeneralParameterDescriptor[] descriptors) { |
| if (parameters == null) { |
| parameters = CC_OperationMethod.group(super.getName(), descriptors); |
| } else { |
| ImplementationHelper.propertyAlreadySet(DefaultOperationMethod.class, "setDescriptors", "parameter"); |
| } |
| } |
| |
| /** |
| * Invoked by {@link AbstractSingleOperation} for completing the parameter descriptor. |
| */ |
| final void updateDescriptors(final GeneralParameterDescriptor[] descriptors) { |
| final ParameterDescriptorGroup previous = parameters; |
| parameters = new DefaultParameterDescriptorGroup(IdentifiedObjects.getProperties(previous), |
| previous.getMinimumOccurs(), previous.getMaximumOccurs(), descriptors); |
| } |
| |
| /** |
| * Invoked by JAXB after unmarshalling. If the {@code <gml:OperationMethod>} element does not contain |
| * any {@code <gml:parameter>}, we assume that this is a valid parameterless operation (as opposed to |
| * an operation with unknown parameters). We need this assumption because, contrarily to GeoAPI model, |
| * the GML schema does not differentiate "no parameters" from "unspecified parameters". |
| */ |
| private void afterUnmarshal(final Unmarshaller unmarshaller, final Object parent) { |
| if (parameters == null) { |
| parameters = CC_OperationMethod.group(super.getName(), new GeneralParameterDescriptor[0]); |
| } |
| } |
| } |