use of org.opengis.parameter.ParameterDescriptorGroup in project sis by apache.
the class DefaultOperationMethodTest method create.
/**
* Creates a new two-dimensional operation method for an operation of the given name and identifier.
*
* @param method the operation name (example: "Mercator (variant A)").
* @param identifier the EPSG numeric identifier (example: "9804").
* @param formula formula citation (example: "EPSG guidance note #7-2").
* @param dimension the number of input and output dimension, or {@code null}.
* @param parameters the parameters (can be empty).
* @return the operation method.
*/
static DefaultOperationMethod create(final String method, final String identifier, final String formula, final Integer dimension, final ParameterDescriptor<?>... parameters) {
final Map<String, Object> properties = new HashMap<>(8);
assertNull(properties.put(OperationMethod.NAME_KEY, method));
assertNull(properties.put(ReferenceIdentifier.CODESPACE_KEY, "EPSG"));
assertNull(properties.put(Identifier.AUTHORITY_KEY, Citations.EPSG));
/*
* The parameter group for a Mercator projection is actually not empty, but it is not the purpose of
* this class to test DefaultParameterDescriptorGroup. So we use an empty group of parameters here.
*/
final ParameterDescriptorGroup pg = new DefaultParameterDescriptorGroup(properties, 1, 1, parameters);
/*
* NAME_KEY share the same Identifier instance for saving a little bit of memory.
* Then define the other properties to be given to OperationMethod.
*/
assertNotNull(properties.put(OperationMethod.NAME_KEY, pg.getName()));
assertNull(properties.put(OperationMethod.IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.EPSG, "EPSG", identifier)));
assertNull(properties.put(OperationMethod.FORMULA_KEY, new DefaultCitation(formula)));
return new DefaultOperationMethod(properties, dimension, dimension, pg);
}
use of org.opengis.parameter.ParameterDescriptorGroup in project sis by apache.
the class SingleOperationMarshallingTest method verifyMethod.
/**
* Verifies the unmarshalled parameter descriptors.
*/
private static void verifyMethod(final OperationMethod method) {
assertIdentifierEquals("name", null, null, null, "Mercator (1SP)", method.getName());
assertEquals("formula", "See EPSG guide.", method.getFormula().getFormula().toString());
assertEquals("sourceDimensions", Integer.valueOf(2), method.getSourceDimensions());
assertEquals("targetDimensions", Integer.valueOf(2), method.getTargetDimensions());
final ParameterDescriptorGroup parameters = method.getParameters();
assertEquals("parameters.name", "Mercator (1SP)", parameters.getName().getCode());
final Iterator<GeneralParameterDescriptor> it = parameters.descriptors().iterator();
CC_OperationParameterGroupTest.verifyMethodParameter(Mercator1SP.LATITUDE_OF_ORIGIN, (ParameterDescriptor<?>) it.next());
CC_OperationParameterGroupTest.verifyMethodParameter(Mercator1SP.LONGITUDE_OF_ORIGIN, (ParameterDescriptor<?>) it.next());
assertFalse("Unexpected parameter.", it.hasNext());
}
use of org.opengis.parameter.ParameterDescriptorGroup in project sis by apache.
the class GeocentricAffine method createParameters.
/**
* Returns the parameters for creating a datum shift operation.
* The operation method will be one of the {@code GeocentricAffine} subclasses,
* unless the specified {@code method} argument is {@link DatumShiftMethod#NONE}.
* If no single operation method can be used, then this method returns {@code null}.
*
* <p>This method does <strong>not</strong> change the coordinate system type.
* The source and target coordinate systems can be both {@code EllipsoidalCS} or both {@code CartesianCS}.
* Any other type or mix of types (e.g. a {@code EllipsoidalCS} source and {@code CartesianCS} target)
* will cause this method to return {@code null}. In such case, it is caller's responsibility to apply
* the datum shift itself in Cartesian geocentric coordinates.</p>
*
* @param sourceCS the source coordinate system. Only the type and number of dimensions is checked.
* @param targetCS the target coordinate system. Only the type and number of dimensions is checked.
* @param datumShift the datum shift as a matrix, or {@code null} if there is no datum shift information.
* @param method the preferred datum shift method. Note that {@code createParameters(…)} may overwrite.
* @return the parameter values, or {@code null} if no single operation method can be found.
*/
public static ParameterValueGroup createParameters(final CoordinateSystem sourceCS, final CoordinateSystem targetCS, final Matrix datumShift, DatumShiftMethod method) {
final boolean isEllipsoidal = (sourceCS instanceof EllipsoidalCS);
if (!(isEllipsoidal ? (targetCS instanceof EllipsoidalCS) : (targetCS instanceof CartesianCS && sourceCS instanceof CartesianCS))) {
// Coordinate systems are not two EllipsoidalCS or two CartesianCS.
return null;
}
@SuppressWarnings("null") int dimension = sourceCS.getDimension();
if (dimension != targetCS.getDimension()) {
// Any value greater than 3 means "mismatched dimensions" for this method.
dimension = 4;
}
if (method == DatumShiftMethod.NONE) {
if (dimension <= 3) {
return Affine.identity(dimension);
} else if (isEllipsoidal) {
final ParameterDescriptorGroup descriptor;
switch(sourceCS.getDimension()) {
case 2:
descriptor = Geographic2Dto3D.PARAMETERS;
break;
case 3:
descriptor = Geographic3Dto2D.PARAMETERS;
break;
default:
return null;
}
return descriptor.createValue();
} else {
return null;
}
}
/*
* Try to convert the matrix into (tX, tY, tZ, rX, rY, rZ, dS) parameters.
* The matrix may not be convertible, in which case we will let the caller
* uses the matrix directly in Cartesian geocentric coordinates.
*/
final BursaWolfParameters parameters = new BursaWolfParameters(null, null);
if (datumShift != null)
try {
parameters.setPositionVectorTransformation(datumShift, BURSAWOLF_TOLERANCE);
} catch (IllegalArgumentException e) {
log(Loggers.COORDINATE_OPERATION, "createParameters", e);
return null;
}
else {
/*
* If there is no datum shift parameters (not to be confused with identity), then those parameters
* are assumed unknown. Using the most accurate methods would give a false impression of accuracy,
* so we use the fastest method instead. Since all parameter values are zero, Apache SIS should use
* the AbridgedMolodenskyTransform2D optimization.
*/
method = DatumShiftMethod.ABRIDGED_MOLODENSKY;
}
final boolean isTranslation = parameters.isTranslation();
final ParameterDescriptorGroup descriptor;
/*
* Following "if" blocks are ordered from most accurate to less accurate datum shift method
* supported by GeocentricAffine subclasses (except NONE which has already been handled).
* Special cases:
*
* - If the datum shift is applied between geocentric CRS, then the Molodensky approximations do not apply
* as they are designed for transformations between geographic CRS only. User preference is then ignored.
*
* - Molodensky methods are approximations for datum shifts having only translation terms in their Bursa-Wolf
* parameters. If there is also a scale or rotation terms, then we can not use Molodensky methods. The user
* preference is then ignored.
*/
if (!isEllipsoidal) {
method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
descriptor = isTranslation ? GeocentricTranslation.PARAMETERS : PositionVector7Param.PARAMETERS;
} else if (!isTranslation) {
method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
descriptor = (dimension >= 3) ? PositionVector7Param3D.PARAMETERS : PositionVector7Param2D.PARAMETERS;
} else
switch(method) {
case GEOCENTRIC_DOMAIN:
{
descriptor = (dimension >= 3) ? GeocentricTranslation3D.PARAMETERS : GeocentricTranslation2D.PARAMETERS;
break;
}
case MOLODENSKY:
{
descriptor = Molodensky.PARAMETERS;
break;
}
case ABRIDGED_MOLODENSKY:
{
descriptor = AbridgedMolodensky.PARAMETERS;
break;
}
default:
throw new AssertionError(method);
}
/*
* Following lines will set all Bursa-Wolf parameter values (scale, translation
* and rotation terms). In the particular case of Molodensky method, we have an
* additional parameter for the number of source and target dimensions (2 or 3).
*/
final Parameters values = createParameters(descriptor, parameters, isTranslation);
switch(method) {
case MOLODENSKY:
case ABRIDGED_MOLODENSKY:
{
if (dimension <= 3) {
values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
}
break;
}
}
return values;
}
use of org.opengis.parameter.ParameterDescriptorGroup in project sis by apache.
the class CC_OperationParameterGroup method merge.
/**
* Invoked by {@link DefaultParameterDescriptorGroup#setDescriptors(GeneralParameterDescriptor[])}
* for merging into a single set the descriptors which are repeated twice in a GML document.
*
* <p>The {@code descriptors} argument gives the descriptors listed explicitely inside a
* {@code <gml:OperationParameterGroup>} or {@code <gml:OperationMethod>} element. Those
* descriptors are said "incomplete" (from SIS point of view) because they are missing the
* {@link ParameterDescriptor#getValueClass()} property, which does not exist in GML but
* is mandatory for us. However an exception to this "incompleteness" happen when SIS has
* been able to match the {@code <gml:OperationMethod>} parent to one of the pre-defined
* operations in the {@link org.apache.sis.internal.referencing.provider} package.</p>
*
* <p>The {@code fromValues} argument gives the descriptors declared in each {@code <gml:ParameterValue>}
* instances of a {@code <gml:ParameterValueGroup>} or {@code <gml:AbstractSingleOperation>} element.
* Contrarily to the {@code descriptors} argument, the {@code fromValues} instances should have non-null
* {@link ParameterDescriptor#getValueClass()} property inferred by SIS from the parameter value.</p>
*
* <p>So the preferred descriptors from more complete to less complete are:</p>
* <ol>
* <li>{@code descriptors} if and only if they contain pre-defined parameters inferred by SIS from the {@code <gml:OperationMethod>} name.</li>
* <li>{@code fromValues}, which contain the descriptors declared in the {@code <gml:ParameterValue>} instances.</li>
* <li>{@code descriptors}, which contain the descriptor listed in {@code <gml:OperationParameterGroup>} or {@code <gml:OperationMethod>}.</li>
* </ol>
*
* <div class="note"><b>Note:</b>
* this code is defined in this {@code CC_OperationParameterGroup} class instead than in the
* {@link DefaultParameterDescriptorGroup} class in the hope to reduce the amount of code
* processed by the JVM in the common case where JAXB (un)marshalling is not needed.</div>
*
* @param descriptors the descriptors declared in the {@code ParameterDescriptorGroup}.
* @param fromValues the descriptors declared in the {@code ParameterValue} instances.
* They are said "valid" because they contain the mandatory {@code valueClass} property.
* @param replacements an {@code IdentityHashMap} where to store the replacements that the caller needs to
* apply in the {@code GeneralParameterValue} instances.
* @return a sequence containing the merged set of parameter descriptors.
*
* @see <a href="http://issues.apache.org/jira/browse/SIS-290">SIS-290</a>
*/
public static GeneralParameterDescriptor[] merge(final List<GeneralParameterDescriptor> descriptors, final GeneralParameterDescriptor[] fromValues, final Map<GeneralParameterDescriptor, GeneralParameterDescriptor> replacements) {
if (descriptors.isEmpty()) {
return fromValues;
}
final Map<String, GeneralParameterDescriptor> union = new LinkedHashMap<>(Containers.hashMapCapacity(descriptors.size()));
/*
* Collect the descriptors declared explicitely in the ParameterDescriptorGroup. We should never have
* two descriptors of the same name since the DefaultParameterDescriptorGroup constructor checked for
* name ambiguity. If a name collision is nevertheless detected, this would mean that a descriptor's
* name mutated.
*/
for (final GeneralParameterDescriptor p : descriptors) {
final String name = p.getName().getCode();
if (union.put(name, p) != null) {
throw new CorruptedObjectException(name);
}
}
/*
* Verify if any descriptors found in the ParameterValue instances could replace the descriptors in the group.
* We give precedence to the descriptors having a non-null 'valueClass' property, which normally appear in the
* 'fromValues' array.
*/
for (final GeneralParameterDescriptor valueDescriptor : fromValues) {
final String name = valueDescriptor.getName().getCode();
GeneralParameterDescriptor complete = valueDescriptor;
GeneralParameterDescriptor previous = union.put(name, complete);
if (previous != null) {
if (previous instanceof ParameterDescriptor<?>) {
verifyEquivalence(name, complete instanceof ParameterDescriptor<?>);
final Class<?> valueClass = ((ParameterDescriptor<?>) previous).getValueClass();
if (valueClass != null) {
/*
* This may happen if the 'descriptors' argument contain the parameters of a pre-defined
* method from the 'org.apache.sis.internal.referencing.provider' package instead than a
* descriptor from the GML file. In such case, presume that 'previous' is actually more
* complete than 'complete'.
*
* Note that 'r' should never be null unless JAXB unmarshalled the elements in reverse
* order (e.g. <gml:ParameterValue> before <gml:OperationMethod>). Since this behavior
* may depend on JAXB implementation, we are better to check for such case.
*/
final Class<?> r = ((ParameterDescriptor<?>) complete).getValueClass();
if (r != null) {
verifyEquivalence(name, valueClass == r);
}
// Restore the previous value in the map and swap 'previous' with 'replacement'.
previous = union.put(name, complete = previous);
}
} else if (previous instanceof ParameterDescriptorGroup) {
verifyEquivalence(name, complete instanceof ParameterDescriptorGroup);
}
/*
* Verify that the replacement contains at least all the information provided by the previous
* descriptor. The replacement is allowed to contain more information however.
*/
final GeneralParameterDescriptor replacement = CC_GeneralOperationParameter.merge(previous, complete);
if (replacement != valueDescriptor) {
union.put(name, replacement);
if (replacements.put(valueDescriptor, replacement) != null) {
// Should never happen, unless the parameter name changed during execution of this loop.
throw new CorruptedObjectException(name);
}
}
}
}
return union.values().toArray(new GeneralParameterDescriptor[union.size()]);
}
use of org.opengis.parameter.ParameterDescriptorGroup in project sis by apache.
the class DefaultOperationMethod method formatTo.
/**
* Formats this operation as a <cite>Well Known Text</cite> {@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.internal.referencing.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.internal.referencing.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 GeneralDerivedCRS) {
final Conversion conversion = ((GeneralDerivedCRS) 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.getResources(formatter.getLocale()).getString(Vocabulary.Keys.Unnamed);
// Because the "Unnamed" string is not a real OperationMethod name.
kind = ElementKind.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.Projection. So strictly speaking only the first check in
* the following 'if' statement is relevant.
*
* 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 Projection. All other operation types are accepted.
*/
final Class<? extends SingleOperation> type = getOperationType();
if (Projection.class.isAssignableFrom(type) || type.isAssignableFrom(Projection.class)) {
return WKTKeywords.Projection;
}
formatter.setInvalidWKT(this, null);
}
return WKTKeywords.Method;
}
Aggregations