use of org.opengis.referencing.operation.ConcatenatedOperation in project sis by apache.
the class CoordinateOperationRegistryTest method verifyNTF.
/**
* Verifies a coordinate operation which is expected to be <cite>"NTF (Paris) to WGS 84 (1)"</cite> (EPSG:8094).
*
* @param domain either {@code "geog2D domain"} or either {@code "geog3D domain"}.
* @param isEPSG {@code true} if the coordinate operation is expected to contain EPSG identifiers.
*/
private static void verifyNTF(final CoordinateOperation operation, final String domain, final boolean isEPSG) {
assertInstanceOf("Operation should have two steps.", ConcatenatedOperation.class, operation);
final List<? extends CoordinateOperation> steps = ((ConcatenatedOperation) operation).getOperations();
assertEquals("Operation should have two steps.", 2, steps.size());
final SingleOperation step1 = (SingleOperation) steps.get(0);
final SingleOperation step2 = (SingleOperation) steps.get(1);
if (isEPSG) {
assertEpsgNameAndIdentifierEqual("NTF (Paris) to WGS 84 (1)", 8094, operation);
assertEpsgNameAndIdentifierEqual("NTF (Paris)", 4807, operation.getSourceCRS());
assertEpsgNameAndIdentifierEqual("WGS 84", 4326, operation.getTargetCRS());
assertEpsgNameAndIdentifierEqual("NTF (Paris) to NTF (1)", 1763, step1);
assertEpsgNameAndIdentifierEqual("NTF to WGS 84 (1)", 1193, step2);
} else {
assertEpsgNameWithoutIdentifierEqual("NTF (Paris) to WGS 84 (1)", operation);
assertEpsgNameWithoutIdentifierEqual("NTF (Paris)", operation.getSourceCRS());
assertEquals("name", "WGS 84", operation.getTargetCRS().getName().getCode());
assertEpsgNameWithoutIdentifierEqual("NTF (Paris) to NTF (1)", step1);
assertEpsgNameWithoutIdentifierEqual("NTF to WGS 84 (1)", step2);
}
assertSame("SourceCRS shall be the targetCRS of previous step.", step1.getTargetCRS(), step2.getSourceCRS());
assertEquals("Method 1", "Longitude rotation", step1.getMethod().getName().getCode());
assertEquals("Method 2", "Geocentric translations (" + domain + ')', step2.getMethod().getName().getCode());
final ParameterValueGroup p1 = step1.getParameterValues();
final ParameterValueGroup p2 = step2.getParameterValues();
assertEquals("Longitude offset", 2.5969213, p1.parameter("Longitude offset").doubleValue(), STRICT);
assertEquals("X-axis translation", -168, p2.parameter("X-axis translation").doubleValue(), STRICT);
assertEquals("Y-axis translation", -60, p2.parameter("Y-axis translation").doubleValue(), STRICT);
assertEquals("Z-axis translation", 320, p2.parameter("Z-axis translation").doubleValue(), STRICT);
assertEquals("linearAccuracy", 2, CRS.getLinearAccuracy(operation), STRICT);
}
use of org.opengis.referencing.operation.ConcatenatedOperation in project sis by apache.
the class PositionalAccuracyConstant method getLinearAccuracy.
/**
* Convenience method returning the accuracy in meters for the specified operation.
* This method tries each of the following procedures and returns the first successful one:
*
* <ul>
* <li>If at least one {@link QuantitativeResult} is found with a linear unit, then the largest
* accuracy estimate is converted to {@linkplain Units#METRE metres} and returned.</li>
* <li>Otherwise, if the operation is a {@link Conversion}, then returns 0 since a conversion
* is by definition accurate up to rounding errors.</li>
* <li>Otherwise, if the operation is a {@link Transformation}, then checks if the datum shift
* were applied with the help of Bursa-Wolf parameters. This procedure looks for SIS-specific
* {@link #DATUM_SHIFT_APPLIED} and {@link #DATUM_SHIFT_OMITTED DATUM_SHIFT_OMITTED} constants.</li>
* <li>Otherwise, if the operation is a {@link ConcatenatedOperation}, returns the sum of the accuracy
* of all components. This is a conservative scenario where we assume that errors cumulate linearly.
* Note that this is not necessarily the "worst case" scenario since the accuracy could be worst
* if the math transforms are highly non-linear.</li>
* </ul>
*
* If the above is modified, please update {@code AbstractCoordinateOperation.getLinearAccuracy()} javadoc.
*
* @param operation the operation to inspect for accuracy.
* @return the accuracy estimate (always in meters), or NaN if unknown.
*
* @see org.apache.sis.referencing.operation.AbstractCoordinateOperation#getLinearAccuracy()
*/
public static double getLinearAccuracy(final CoordinateOperation operation) {
double accuracy = Double.NaN;
final Collection<PositionalAccuracy> accuracies = operation.getCoordinateOperationAccuracy();
for (final PositionalAccuracy metadata : accuracies) {
for (final Result result : metadata.getResults()) {
if (result instanceof QuantitativeResult) {
final QuantitativeResult quantity = (QuantitativeResult) result;
final Collection<? extends Record> records = quantity.getValues();
if (records != null) {
final Unit<?> unit = quantity.getValueUnit();
if (Units.isLinear(unit)) {
final Unit<Length> unitOfLength = unit.asType(Length.class);
for (final Record record : records) {
for (final Object value : record.getAttributes().values()) {
if (value instanceof Number) {
double v = ((Number) value).doubleValue();
v = unitOfLength.getConverterTo(Units.METRE).convert(v);
if (v >= 0 && !(v <= accuracy)) {
// '!' is for replacing the NaN value.
accuracy = v;
}
}
}
}
}
}
}
}
}
if (Double.isNaN(accuracy)) {
/*
* No quantitative (linear) accuracy were found. If the coordinate operation is actually
* a conversion, the accuracy is up to rounding error (i.e. conceptually 0) by definition.
*/
if (operation instanceof Conversion) {
return 0;
}
/*
* If the coordinate operation is actually a transformation, checks if Bursa-Wolf parameters
* were available for the datum shift. This is SIS-specific. See field javadoc for a rational
* about the return values chosen.
*/
if (operation instanceof Transformation) {
if (accuracies.contains(DATUM_SHIFT_APPLIED)) {
return DATUM_SHIFT_ACCURACY;
}
if (accuracies.contains(DATUM_SHIFT_OMITTED)) {
return UNKNOWN_ACCURACY;
}
}
/*
* If the coordinate operation is a compound of other coordinate operations, returns the sum of their accuracy,
* skipping unknown ones. Making the sum is a conservative approach (not exactly the "worst case" scenario,
* since it could be worst if the transforms are highly non-linear).
*/
if (operation instanceof ConcatenatedOperation) {
for (final CoordinateOperation op : ((ConcatenatedOperation) operation).getOperations()) {
final double candidate = Math.abs(getLinearAccuracy(op));
if (!Double.isNaN(candidate)) {
if (Double.isNaN(accuracy)) {
accuracy = candidate;
} else {
accuracy += candidate;
}
}
}
}
}
return accuracy;
}
use of org.opengis.referencing.operation.ConcatenatedOperation in project sis by apache.
the class Formatter method appendComplement.
/**
* Appends the optional complementary attributes common to many {@link IdentifiedObject} subtypes.
* Those attributes are {@code ANCHOR}, {@code SCOPE}, {@code AREA}, {@code BBOX}, {@code VERTICALEXTENT},
* {@code TIMEEXTENT}, {@code ID} (previously known as {@code AUTHORITY}) and {@code REMARKS},
* and have a special treatment: they are written by {@link #append(FormattableObject)}
* after the {@code formatTo(Formatter)} method returned.
*
* <p>The {@code ID[<name>,<code>,…]} element is normally written only for the root element
* (unless the convention is {@code INTERNAL}), but there is various exceptions to this rule.
* If formatted, the {@code ID} element will be by default on the same line than the enclosing
* element (e.g. {@code SPHEROID["Clarke 1866", …, ID["EPSG", 7008]]}). Other example:</p>
*
* {@preformat text
* PROJCS["NAD27 / Idaho Central",
* GEOGCS[...etc...],
* ...etc...
* ID["EPSG", 26769]]
* }
*
* For non-internal conventions, all elements other than {@code ID[…]} are formatted
* only for {@link CoordinateOperation} and root {@link ReferenceSystem} instances,
* with an exception for remarks of {@code ReferenceSystem} embedded inside {@code CoordinateOperation}.
* Those restrictions are our interpretation of the following ISO 19162 requirement:
*
* <blockquote>(…snip…) {@code <scope extent identifier remark>} is a collection of four optional attributes
* which may be applied to a coordinate reference system, a coordinate operation or a boundCRS. (…snip…)
* Identifier (…snip…) may also be utilised for components of these objects although this is not recommended
* except for coordinate operation methods (including map projections) and parameters. (…snip…)
* A {@code <remark>} can be included within the descriptions of source and target CRS embedded within
* a coordinate transformation as well as within the coordinate transformation itself.</blockquote>
*/
@SuppressWarnings("null")
private void appendComplement(final IdentifiedObject object, final FormattableObject parent, final FormattableObject gp) {
isComplement = true;
// Whether to format ID[…] elements.
final boolean showIDs;
// Whether we shall limit to a single ID[…] element.
final boolean filterID;
// Whether to format any element other than ID[…] and Remarks[…].
final boolean showOthers;
// Whether to format Remarks[…].
final boolean showRemarks;
if (convention == Convention.INTERNAL) {
showIDs = true;
filterID = false;
showOthers = true;
showRemarks = true;
} else {
/*
* Except for the special cases of OperationMethod and Parameters, ISO 19162 recommends to format the
* ID only for the root element. But Apache SIS adds an other exception to this rule by handling the
* components of CompoundCRS as if they were root elements. The reason is that users often create their
* own CompoundCRS from standard components, for example by adding a time axis to some standard CRS like
* "WGS84". The resulting CompoundCRS usually have no identifier. Then the users often need to extract a
* particular component of a CompoundCRS, most often the horizontal part, and will need its identifier
* for example in a Web Map Service (WMS). Those ID are lost if we do not format them here.
*/
if (parent == null || parent instanceof CompoundCRS) {
showIDs = true;
} else if (gp instanceof CoordinateOperation && !(parent instanceof IdentifiedObject)) {
// "SourceCRS[…]" and "TargetCRS[…]" sub-elements in CoordinateOperation.
showIDs = true;
} else if (convention == Convention.WKT2_SIMPLIFIED) {
showIDs = false;
} else {
showIDs = (object instanceof OperationMethod) || (object instanceof GeneralParameterDescriptor);
}
if (convention.majorVersion() == 1) {
filterID = true;
showOthers = false;
showRemarks = false;
} else {
filterID = (parent != null);
if (object instanceof CoordinateOperation) {
showOthers = !(parent instanceof ConcatenatedOperation);
showRemarks = showOthers;
} else if (object instanceof ReferenceSystem) {
showOthers = (parent == null);
showRemarks = (parent == null) || (gp instanceof CoordinateOperation);
} else {
// Mandated by ISO 19162.
showOthers = false;
showRemarks = false;
}
}
}
if (showOthers) {
appendForSubtypes(object);
}
if (showIDs) {
Collection<ReferenceIdentifier> identifiers = object.getIdentifiers();
if (identifiers != null) {
// Paranoiac check
if (filterID) {
for (final ReferenceIdentifier id : identifiers) {
if (Citations.identifierMatches(authority, id.getAuthority())) {
identifiers = Collections.singleton(id);
break;
}
}
}
for (ReferenceIdentifier id : identifiers) {
if (!(id instanceof FormattableObject)) {
id = ImmutableIdentifier.castOrCopy(id);
}
append((FormattableObject) id);
if (filterID)
break;
}
}
}
if (showRemarks) {
appendOnNewLine(WKTKeywords.Remark, object.getRemarks(), ElementKind.REMARKS);
}
isComplement = false;
}
use of org.opengis.referencing.operation.ConcatenatedOperation in project sis by apache.
the class TransformCommand method printOperations.
/**
* Prints a summary of the given coordinate operation as a sequence of steps.
* If the operations is specified by EPSG, prints the EPSG name and code.
* Otherwise prints only the operation method names, since the coordinate operation names
* generated by SIS are not very meaningful.
*
* @param step the coordinate operation to print as a sequence of steps.
*/
private void printOperations(final CoordinateOperation step, boolean isNext) {
if (isNext) {
isNext = false;
if (colors) {
outHeader.append(X364.FOREGROUND_GREEN.sequence());
}
outHeader.append(" → ");
if (colors) {
outHeader.append(X364.FOREGROUND_DEFAULT.sequence());
}
}
if (!printNameAndIdentifier(step, true)) {
if (step instanceof ConcatenatedOperation) {
for (final CoordinateOperation op : ((ConcatenatedOperation) step).getOperations()) {
printOperations(op, isNext);
isNext = true;
}
} else if (step instanceof PassThroughOperation) {
printOperations(((PassThroughOperation) step).getOperation(), false);
} else if (step instanceof SingleOperation) {
outHeader.append(((SingleOperation) step).getMethod().getName().getCode());
}
}
}
use of org.opengis.referencing.operation.ConcatenatedOperation in project sis by apache.
the class IdentifiedObjects method lookupURN.
/**
* Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:9.1:4326"}, of the specified object.
* This method searches in all {@linkplain org.apache.sis.referencing.factory.GeodeticAuthorityFactory geodetic
* authority factories} known to SIS for an object {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATIVE
* approximatively equals} to the specified object. Then there is a choice:
*
* <ul>
* <li>If a single matching object is found in the specified authority factory, then its URN is returned.</li>
* <li>Otherwise if the given object is a {@link CompoundCRS} or {@link ConcatenatedOperation}
* and all components have an URN, then this method returns a combined URN.</li>
* <li>Otherwise this method returns {@code null}.</li>
* </ul>
*
* <p><strong>Note that this method checks the identifier validity.</strong>
* If the given object declares explicitly an identifier, then this method will instantiate an object from the
* authority factory using that identifier and compare it with the given object. If the comparison fails, then
* this method returns {@code null}. Consequently this method may return {@code null} even if the given object
* declares explicitly its identifier. If the declared identifier is wanted unconditionally,
* one can use the following pattern instead:
*
* {@preformat java
* String urn = toURN(object.getClass(), getIdentifier(object, authority));
* }
*
* This method can be seen as a converse of {@link CRS#forCode(String)}.
*
* @param object the object (usually a {@linkplain org.apache.sis.referencing.crs.AbstractCRS
* coordinate reference system}) whose identifier is to be found, or {@code null}.
* @param authority the authority for the identifier to return, or {@code null} for
* the first identifier regardless its authority.
* @return the identifier, or {@code null} if none was found without ambiguity or if the given object was null.
* @throws FactoryException if an error occurred during the search.
*
* @see #newFinder(String)
* @see #toURN(Class, Identifier)
*
* @since 0.7
*/
public static String lookupURN(final IdentifiedObject object, final Citation authority) throws FactoryException {
if (object == null) {
return null;
}
IdentifiedObjectFinder finder;
try {
finder = newFinder(Citations.getCodeSpace(authority));
} catch (NoSuchAuthorityFactoryException e) {
warning("lookupURN", e);
finder = newFinder(null);
}
String urn = lookupURN(object, authority, finder);
if (urn != null) {
return urn;
}
/*
* If we didn't found a URN but the given object is made of smaller components, build a combined URN.
* Example: "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701" (without spaces actually).
*/
final List<? extends IdentifiedObject> components;
if (object instanceof CompoundCRS) {
components = CRS.getSingleComponents((CompoundCRS) object);
} else if (object instanceof ConcatenatedOperation) {
components = ((ConcatenatedOperation) object).getOperations();
} else {
return null;
}
StringBuilder buffer = null;
for (final IdentifiedObject component : components) {
urn = lookupURN(component, authority, finder);
if (urn == null) {
return null;
}
assert urn.startsWith(DefinitionURI.PREFIX) : urn;
if (buffer == null) {
buffer = new StringBuilder(40).append(DefinitionURI.PREFIX).append(DefinitionURI.SEPARATOR).append(NameMeaning.toObjectType(object.getClass()));
}
buffer.append(DefinitionURI.COMPONENT_SEPARATOR).append(urn, DefinitionURI.PREFIX.length() + 1, urn.length());
}
return (buffer != null) ? buffer.toString() : null;
}
Aggregations