use of org.apache.sis.internal.referencing.DeferredCoordinateOperation in project sis by apache.
the class EPSGDataAccess method createCoordinateOperation.
/**
* Creates an operation for transforming coordinates in the source CRS to coordinates in the target CRS.
* The returned object will either be a {@link Conversion} or a {@link Transformation}, depending on the code.
*
* <div class="note"><b>Example:</b>
* some EPSG codes for coordinate transformations are:
*
* <table class="sis" summary="EPSG codes examples">
* <tr><th>Code</th> <th>Description</th></tr>
* <tr><td>1133</td> <td>ED50 to WGS 84 (1)</td></tr>
* <tr><td>1241</td> <td>NAD27 to NAD83 (1)</td></tr>
* <tr><td>1173</td> <td>NAD27 to WGS 84 (4)</td></tr>
* <tr><td>6326</td> <td>NAD83(2011) to NAVD88 height (1)</td></tr>
* </table></div>
*
* @param code value allocated by EPSG.
* @return the operation for the given code.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*/
@Override
@SuppressWarnings("null")
public synchronized CoordinateOperation createCoordinateOperation(final String code) throws NoSuchAuthorityCodeException, FactoryException {
ArgumentChecks.ensureNonNull("code", code);
CoordinateOperation returnValue = null;
try {
try (ResultSet result = executeQuery("Coordinate_Operation", "COORD_OP_CODE", "COORD_OP_NAME", "SELECT COORD_OP_CODE," + " COORD_OP_NAME," + " COORD_OP_TYPE," + " SOURCE_CRS_CODE," + " TARGET_CRS_CODE," + " COORD_OP_METHOD_CODE," + " COORD_TFM_VERSION," + " COORD_OP_ACCURACY," + " AREA_OF_USE_CODE," + " COORD_OP_SCOPE," + " REMARKS," + " DEPRECATED" + " FROM [Coordinate_Operation]" + " WHERE COORD_OP_CODE = ?", code)) {
while (result.next()) {
final Integer epsg = getInteger(code, result, 1);
final String name = getString(code, result, 2);
final String type = getString(code, result, 3).toLowerCase(Locale.US);
final boolean isTransformation = type.equals("transformation");
final boolean isConversion = type.equals("conversion");
final boolean isConcatenated = type.equals("concatenated operation");
final String sourceCode, targetCode;
final Integer methodCode;
if (isConversion) {
// Optional for conversions, mandatory for all others.
sourceCode = getOptionalString(result, 4);
targetCode = getOptionalString(result, 5);
} else {
sourceCode = getString(code, result, 4);
targetCode = getString(code, result, 5);
}
if (isConcatenated) {
// Not applicable to concatenated operation, mandatory for all others.
methodCode = getOptionalInteger(result, 6);
} else {
methodCode = getInteger(code, result, 6);
}
final String version = getOptionalString(result, 7);
final double accuracy = getOptionalDouble(result, 8);
final String area = getOptionalString(result, 9);
final String scope = getOptionalString(result, 10);
final String remarks = getOptionalString(result, 11);
final boolean deprecated = getOptionalBoolean(result, 12);
/*
* Create the source and target CRS for the codes fetched above. Those CRS are optional only for
* conversions (the above calls to getString(code, result, …) verified that those CRS are defined
* for other kinds of operation). Conversions in EPSG database are usually "defining conversions"
* without source and target CRS.
*
* In EPSG database 6.7, all defining conversions are projections and their dimensions are always 2.
* However, this default number of dimensions is not generalizable to other kind of operation methods.
* For example the "Geocentric translation" operation method has 3-dimensional source and target CRS.
*/
boolean isDimensionKnown = true;
final int sourceDimensions, targetDimensions;
final CoordinateReferenceSystem sourceCRS, targetCRS;
if (sourceCode != null) {
sourceCRS = owner.createCoordinateReferenceSystem(sourceCode);
sourceDimensions = sourceCRS.getCoordinateSystem().getDimension();
} else {
sourceCRS = null;
// Acceptable default for projections only.
sourceDimensions = 2;
isDimensionKnown = false;
}
if (targetCode != null) {
targetCRS = owner.createCoordinateReferenceSystem(targetCode);
targetDimensions = targetCRS.getCoordinateSystem().getDimension();
} else {
targetCRS = null;
// Acceptable default for projections only.
targetDimensions = 2;
isDimensionKnown = false;
}
/*
* Get the operation method. This is mandatory for conversions and transformations
* (it was checked by getInteger(code, result, …) above in this method) but optional
* for concatenated operations. Fetching parameter values is part of this block.
*/
final boolean isDeferred = Semaphores.query(Semaphores.METADATA_ONLY);
ParameterValueGroup parameters = null;
OperationMethod method = null;
if (methodCode != null && !isDeferred) {
method = owner.createOperationMethod(methodCode.toString());
if (isDimensionKnown) {
method = DefaultOperationMethod.redimension(method, sourceDimensions, targetDimensions);
}
parameters = method.getParameters().createValue();
fillParameterValues(methodCode, epsg, parameters);
}
/*
* Creates common properties. The 'version' and 'accuracy' are usually defined
* for transformations only. However, we check them for all kind of operations
* (including conversions) and copy the information unconditionally if present.
*
* NOTE: This block must be executed last before object creations below, because
* methods like createCoordinateReferenceSystem and createOperationMethod
* overwrite the properties map.
*/
Map<String, Object> opProperties = createProperties("Coordinate_Operation", name, epsg, area, scope, remarks, deprecated);
opProperties.put(CoordinateOperation.OPERATION_VERSION_KEY, version);
if (!Double.isNaN(accuracy)) {
opProperties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, TransformationAccuracy.create(accuracy));
}
/*
* Creates the operation. Conversions should be the only operations allowed to have
* null source and target CRS. In such case, the operation is a defining conversion
* (usually to be used later as part of a ProjectedCRS creation).
*/
final CoordinateOperation operation;
final CoordinateOperationFactory copFactory = owner.copFactory;
if (isDeferred) {
operation = new DeferredCoordinateOperation(opProperties, sourceCRS, targetCRS, owner);
} else if (isConversion && (sourceCRS == null || targetCRS == null)) {
operation = copFactory.createDefiningConversion(opProperties, method, parameters);
} else if (isConcatenated) {
/*
* Concatenated operation: we need to close the current result set, because
* we are going to invoke this method recursively in the following lines.
*/
result.close();
// Because this class uses a shared map.
opProperties = new HashMap<>(opProperties);
final List<String> codes = new ArrayList<>();
try (ResultSet cr = executeQuery("Coordinate_Operation Path", "SELECT SINGLE_OPERATION_CODE" + " FROM [Coordinate_Operation Path]" + " WHERE (CONCAT_OPERATION_CODE = ?)" + " ORDER BY OP_PATH_STEP", epsg)) {
while (cr.next()) {
codes.add(getString(code, cr, 1));
}
}
final CoordinateOperation[] operations = new CoordinateOperation[codes.size()];
ensureNoCycle(CoordinateOperation.class, epsg);
try {
for (int i = 0; i < operations.length; i++) {
operations[i] = owner.createCoordinateOperation(codes.get(i));
}
} finally {
endOfRecursivity(CoordinateOperation.class, epsg);
}
return copFactory.createConcatenatedOperation(opProperties, operations);
} else {
/*
* At this stage, the parameters are ready for use. Create the math transform and wrap it in the
* final operation (a Conversion or a Transformation). We need to give to MathTransformFactory
* some information about the context (source and target CRS) for allowing the factory to set
* the values of above-mentioned implicit parameters (semi-major and semi-minor axis lengths).
*
* The first special case may be removed in a future SIS version if the missing method is added
* to GeoAPI. Actually GeoAPI has a method doing part of the job, but incomplete (e.g. the pure
* GeoAPI method can not handle Molodensky transform because it does not give the target datum).
*/
final MathTransform mt;
final MathTransformFactory mtFactory = owner.mtFactory;
if (mtFactory instanceof DefaultMathTransformFactory) {
mt = ((DefaultMathTransformFactory) mtFactory).createParameterizedTransform(parameters, ReferencingUtilities.createTransformContext(sourceCRS, targetCRS, null));
} else {
// Fallback for non-SIS implementations. Work for map projections but not for Molodensky.
mt = mtFactory.createBaseToDerived(sourceCRS, parameters, targetCRS.getCoordinateSystem());
}
/*
* Give a hint to the factory about the type of the coordinate operation. ISO 19111 defines
* Conversion and Transformation, but SIS also have more specific sub-types. We begin with
* what we can infer from the EPSG database. Next, if the SIS MathTransform providers give
* more information, then we refine the type.
*/
Class<? extends SingleOperation> opType;
if (isTransformation) {
opType = Transformation.class;
} else if (isConversion) {
opType = Conversion.class;
} else {
opType = SingleOperation.class;
}
final OperationMethod provider = mtFactory.getLastMethodUsed();
if (provider instanceof DefaultOperationMethod) {
// SIS-specific
final Class<?> s = ((DefaultOperationMethod) provider).getOperationType();
if (s != null && opType.isAssignableFrom(s)) {
opType = s.asSubclass(SingleOperation.class);
}
}
opProperties.put(ReferencingServices.OPERATION_TYPE_KEY, opType);
opProperties.put(ReferencingServices.PARAMETERS_KEY, parameters);
/*
* Following restriction will be removed in a future SIS version if the method is added to GeoAPI.
*/
if (!(copFactory instanceof DefaultCoordinateOperationFactory)) {
throw new UnsupportedOperationException(error().getString(Errors.Keys.UnsupportedImplementation_1, copFactory.getClass()));
}
operation = ((DefaultCoordinateOperationFactory) copFactory).createSingleOperation(opProperties, sourceCRS, targetCRS, null, method, mt);
}
returnValue = ensureSingleton(operation, returnValue, code);
if (result.isClosed()) {
return returnValue;
}
}
}
} catch (SQLException exception) {
throw databaseFailure(CoordinateOperation.class, code, exception);
}
if (returnValue == null) {
throw noSuchAuthorityCode(CoordinateOperation.class, code);
}
return returnValue;
}
use of org.apache.sis.internal.referencing.DeferredCoordinateOperation in project sis by apache.
the class CoordinateOperationRegistry method search.
/**
* Returns operations for conversions or transformations between two coordinate reference systems.
* This method extracts the authority code from the supplied {@code sourceCRS} and {@code targetCRS},
* and submit them to the {@link #registry}. If no operation is found for those codes, then this method
* returns {@code null}.
*
* @param sourceCRS source coordinate reference system.
* @param targetCRS target coordinate reference system.
* @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}, or {@code null}
* if no such operation is explicitly defined in the underlying database.
* @throws IllegalArgumentException if the coordinate systems are not of the same type or axes do not match.
* @throws IncommensurableException if the units are not compatible or a unit conversion is non-linear.
* @throws FactoryException if an error occurred while creating the operation.
*/
private List<CoordinateOperation> search(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS) throws IllegalArgumentException, IncommensurableException, FactoryException {
final List<String> sources = findCode(sourceCRS);
if (sources.isEmpty())
return null;
final List<String> targets = findCode(targetCRS);
if (targets.isEmpty())
return null;
final List<CoordinateOperation> operations = new ArrayList<>();
boolean foundDirectOperations = false;
boolean useDeprecatedOperations = false;
for (final String sourceID : sources) {
for (final String targetID : targets) {
if (sourceID.equals(targetID)) {
/*
* Above check is necessary because this method may be invoked in some situations where the code
* are equal while the CRS are not. Such situation should be illegal, but unfortunately it still
* happen because many software products are not compliant with EPSG definition of axis order.
* In such cases we will need to compute a transform from sourceCRS to targetCRS ignoring the
* source and target codes. The CoordinateOperationFinder class can do that, providing that we
* prevent this CoordinateOperationRegistry to (legitimately) claims that the operation from
* sourceCode to targetCode is the identity transform.
*/
return null;
}
/*
* Some pairs of CRS have a lot of coordinate operations backed by datum shift grids.
* We do not want to load all of them until we found the right coordinate operation.
* The non-public Semaphores.METADATA_ONLY mechanism instructs EPSGDataAccess to
* instantiate DeferredCoordinateOperation instead of full coordinate operations.
*/
final boolean mdOnly = Semaphores.queryAndSet(Semaphores.METADATA_ONLY);
try {
Collection<CoordinateOperation> authoritatives;
try {
authoritatives = registry.createFromCoordinateReferenceSystemCodes(sourceID, targetID);
final boolean inverse = Containers.isNullOrEmpty(authoritatives);
if (inverse) {
/*
* No operation from 'source' to 'target' available. But maybe there is an inverse operation.
* This is typically the case when the user wants to convert from a projected to a geographic CRS.
* The EPSG database usually contains transformation paths for geographic to projected CRS only.
*/
if (foundDirectOperations) {
// Ignore inverse operations if we already have direct ones.
continue;
}
authoritatives = registry.createFromCoordinateReferenceSystemCodes(targetID, sourceID);
if (Containers.isNullOrEmpty(authoritatives)) {
continue;
}
} else if (!foundDirectOperations) {
foundDirectOperations = true;
// Keep only direct operations.
operations.clear();
}
} catch (NoSuchAuthorityCodeException | MissingFactoryResourceException e) {
/*
* sourceCode or targetCode is unknown to the underlying authority factory.
* Ignores the exception and fallback on the generic algorithm provided by
* CoordinateOperationFinder.
*/
log(null, e);
continue;
}
/*
* If we found at least one non-deprecated operation, we will stop the search at
* the first deprecated one (assuming that deprecated operations are sorted last).
* Deprecated operations are kept only if there is no non-deprecated operations.
*/
try {
for (final CoordinateOperation candidate : authoritatives) {
if (candidate != null) {
// Paranoiac check.
if ((candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated()) {
if (!useDeprecatedOperations && !operations.isEmpty())
break;
useDeprecatedOperations = true;
} else if (useDeprecatedOperations) {
useDeprecatedOperations = false;
// Replace deprecated operations by non-deprecated ones.
operations.clear();
}
operations.add(candidate);
}
}
} catch (BackingStoreException exception) {
throw exception.unwrapOrRethrow(FactoryException.class);
}
} finally {
if (!mdOnly) {
Semaphores.clear(Semaphores.METADATA_ONLY);
}
}
}
}
/*
* At this point we got the list of coordinate operations. Now, sort them in preference order.
* We will loop over all coordinate operations and select the one having the largest intersection
* with the area of interest. Note that if the user did not specified an area of interest himself,
* then we need to get one from the CRS. This is necessary for preventing the transformation from
* NAD27 to NAD83 in Idaho to select the transform for Alaska (since the later has a larger area).
*/
CoordinateOperationSorter.sort(operations, Extents.getGeographicBoundingBox(areaOfInterest));
final ListIterator<CoordinateOperation> it = operations.listIterator();
while (it.hasNext()) {
/*
* At this point we filtered a CoordinateOperation by looking only at its metadata.
* Code following this point will need the full coordinate operation, including its
* MathTransform. So if we got a deferred operation, we need to resolve it now.
* Conversely, we should not use metadata below this point because the call to
* inverse(CoordinateOperation) is not guaranteed to preserve all metadata.
*/
CoordinateOperation operation = it.next();
try {
if (operation instanceof DeferredCoordinateOperation) {
operation = ((DeferredCoordinateOperation) operation).create();
}
if (operation instanceof SingleOperation && operation.getMathTransform() == null) {
operation = fromDefiningConversion((SingleOperation) operation, foundDirectOperations ? sourceCRS : targetCRS, foundDirectOperations ? targetCRS : sourceCRS);
if (operation == null) {
it.remove();
continue;
}
}
if (!foundDirectOperations) {
operation = inverse(operation);
}
} catch (NoninvertibleTransformException | MissingFactoryResourceException e) {
/*
* If we failed to get the real CoordinateOperation instance, remove it from
* the collection and try again in order to get the next best choices.
*/
log(null, e);
it.remove();
// Try again with the next best case.
continue;
}
/*
* It is possible that the CRS given to this method were not quite right. For example the user
* may have created his CRS from a WKT using a different axis order than the order specified by
* the authority and still (wrongly) call those CRS "EPSG:xxxx". So we check if the source and
* target CRS for the operation we just created are equivalent to the CRS specified by the user.
*
* NOTE: FactoryException may be thrown if we fail to create a transform from the user-provided
* CRS to the authority-provided CRS. That transform should have been only an identity transform,
* or a simple affine transform if the user specified wrong CRS as explained in above paragraph.
* If we fail here, we are likely to fail for all other transforms. So we are better to let the
* FactoryException propagate.
*/
operation = complete(operation, sourceCRS, targetCRS);
if (filter(operation)) {
if (stopAtFirst) {
operations.clear();
operations.add(operation);
break;
}
it.set(operation);
} else {
it.remove();
}
}
return operations;
}
Aggregations