use of org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory 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.referencing.operation.transform.DefaultMathTransformFactory in project sis by apache.
the class DefaultCoordinateOperationFactory method getOperationMethod.
/**
* Returns the operation method of the given name. The given argument shall be either a method
* {@linkplain DefaultOperationMethod#getName() name} (e.g. <cite>"Transverse Mercator"</cite>)
* or one of its {@linkplain DefaultOperationMethod#getIdentifiers() identifiers} (e.g. {@code "EPSG:9807"}).
* The search is case-insensitive and comparisons against method names can be
* {@linkplain DefaultOperationMethod#isHeuristicMatchForName(String) heuristic}.
*
* <p>If more than one method match the given name, then the first (according iteration order)
* non-{@linkplain org.apache.sis.util.Deprecable#isDeprecated() deprecated} matching method is returned.
* If all matching methods are deprecated, the first one is returned.</p>
*
* @param name the name of the operation method to fetch.
* @return the operation method of the given name.
* @throws FactoryException if the requested operation method can not be fetched.
*
* @see DefaultMathTransformFactory#getOperationMethod(String)
*/
public OperationMethod getOperationMethod(String name) throws FactoryException {
name = CharSequences.trimWhitespaces(name);
ArgumentChecks.ensureNonEmpty("name", name);
final MathTransformFactory mtFactory = getMathTransformFactory();
if (mtFactory instanceof DefaultMathTransformFactory) {
return ((DefaultMathTransformFactory) mtFactory).getOperationMethod(name);
}
final OperationMethod method = ReferencingServices.getInstance().getOperationMethod(mtFactory.getAvailableMethods(SingleOperation.class), name);
if (method != null) {
return method;
}
throw new NoSuchIdentifierException(Resources.forProperties(defaultProperties).getString(Resources.Keys.NoSuchOperationMethod_1, name), name);
}
use of org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory in project sis by apache.
the class CoordinateOperationFinder method createOperationStep.
/**
* Creates an operation between two geodetic (geographic or geocentric) coordinate reference systems.
* The default implementation can:
*
* <ul>
* <li>adjust axis order and orientation, for example converting from (<cite>North</cite>, <cite>West</cite>)
* axes to (<cite>East</cite>, <cite>North</cite>) axes,</li>
* <li>apply units conversion if needed,</li>
* <li>perform longitude rotation if needed,</li>
* <li>perform datum shift if {@linkplain BursaWolfParameters Bursa-Wolf parameters} are available
* for the area of interest.</li>
* </ul>
*
* <p>This method returns only <em>one</em> step for a chain of concatenated operations (to be built by the caller).
* But a list is returned because the same step may be implemented by different operation methods. Only one element
* in the returned list should be selected (usually the first one).</p>
*
* @param sourceCRS input coordinate reference system.
* @param targetCRS output coordinate reference system.
* @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
* @throws FactoryException if the operation can not be constructed.
*/
@SuppressWarnings("null")
protected List<CoordinateOperation> createOperationStep(final GeodeticCRS sourceCRS, final GeodeticCRS targetCRS) throws FactoryException {
final GeodeticDatum sourceDatum = sourceCRS.getDatum();
final GeodeticDatum targetDatum = targetCRS.getDatum();
Matrix datumShift = null;
/*
* If the prime meridian is not the same, we will concatenate a longitude rotation before or after datum shift
* (that concatenation will be performed by the customized DefaultMathTransformFactory.Context created below).
* Actually we do not know if the longitude rotation should be before or after datum shift. But this ambiguity
* can usually be ignored because Bursa-Wolf parameters are always used with source and target prime meridians
* set to Greenwich in EPSG dataset 8.9. For safety, the SIS's DefaultGeodeticDatum class ensures that if the
* prime meridians are not the same, then the target meridian must be Greenwich.
*/
final DefaultMathTransformFactory.Context context = ReferencingUtilities.createTransformContext(sourceCRS, targetCRS, new MathTransformContext(sourceDatum, targetDatum));
/*
* If both CRS use the same datum and the same prime meridian, then the coordinate operation is only axis
* swapping, unit conversion or change of coordinate system type (Ellipsoidal ↔ Cartesian ↔ Spherical).
* Otherwise (if the datum are not the same), we will need to perform a scale, translation and rotation
* in Cartesian space using the Bursa-Wolf parameters. If the user does not require the best accuracy,
* then the Molodensky approximation may be used for avoiding the conversion step to geocentric CRS.
*/
Identifier identifier;
boolean isGeographicToGeocentric = false;
final CoordinateSystem sourceCS = context.getSourceCS();
final CoordinateSystem targetCS = context.getTargetCS();
if (equalsIgnoreMetadata(sourceDatum, targetDatum)) {
final boolean isGeocentricToGeographic;
isGeographicToGeocentric = (sourceCS instanceof EllipsoidalCS && targetCS instanceof CartesianCS);
isGeocentricToGeographic = (sourceCS instanceof CartesianCS && targetCS instanceof EllipsoidalCS);
/*
* Above booleans should never be true in same time. If it nevertheless happen (we are paranoiac;
* maybe a lazy user implemented all interfaces in a single class), do not apply any geographic ↔
* geocentric conversion. Instead do as if the coordinate system types were the same.
*/
if (isGeocentricToGeographic ^ isGeographicToGeocentric) {
identifier = GEOCENTRIC_CONVERSION;
} else {
identifier = AXIS_CHANGES;
}
} else {
identifier = ELLIPSOID_CHANGE;
if (sourceDatum instanceof DefaultGeodeticDatum) {
datumShift = ((DefaultGeodeticDatum) sourceDatum).getPositionVectorTransformation(targetDatum, areaOfInterest);
if (datumShift != null) {
identifier = DATUM_SHIFT;
}
}
}
/*
* Conceptually, all transformations below could done by first converting from the source coordinate
* system to geocentric Cartesian coordinates (X,Y,Z), apply an affine transform represented by the
* datum shift matrix, then convert from the (X′,Y′,Z′) coordinates to the target coordinate system.
* However there is two exceptions to this path:
*
* 1) In the particular where both the source and target CS are ellipsoidal, we may use the
* Molodensky approximation as a shortcut (if the desired accuracy allows).
*
* 2) Even if we really go through the XYZ coordinates without Molodensky approximation, there is
* at least 9 different ways to name this operation depending on whether the source and target
* CRS are geocentric or geographic, 2- or 3-dimensional, whether there is a translation or not,
* the rotation sign, etc. We try to use the most specific name if we can find one, and fallback
* on an arbitrary name only in last resort.
*/
final DefaultMathTransformFactory mtFactory = factorySIS.getDefaultMathTransformFactory();
MathTransform before = null, after = null;
ParameterValueGroup parameters;
if (identifier == DATUM_SHIFT || identifier == ELLIPSOID_CHANGE) {
/*
* If the transform can be represented by a single coordinate operation, returns that operation.
* Possible operations are:
*
* - Position Vector transformation (in geocentric, geographic-2D or geographic-3D domains)
* - Geocentric translation (in geocentric, geographic-2D or geographic-3D domains)
* - [Abridged] Molodensky (as an approximation of geocentric translation)
* - Identity (if the desired accuracy is so large than we can skip datum shift)
*
* TODO: if both CS are ellipsoidal but with different number of dimensions, then we should use
* an intermediate 3D geographic CRS in order to enable the use of Molodensky method if desired.
*/
final DatumShiftMethod preferredMethod = DatumShiftMethod.forAccuracy(desiredAccuracy);
parameters = GeocentricAffine.createParameters(sourceCS, targetCS, datumShift, preferredMethod);
if (parameters == null) {
/*
* Failed to select a coordinate operation. Maybe because the coordinate system types are not the same.
* Convert unconditionally to XYZ geocentric coordinates and apply the datum shift in that CS space.
*
* TODO: operation name should not be "Affine" if 'before' or 'after' transforms are not identity.
* Reminder: the parameter group name here determines the OperationMethod later in this method.
*/
if (datumShift != null) {
parameters = TensorParameters.WKT1.createValueGroup(properties(Constants.AFFINE), datumShift);
} else {
// Dimension of geocentric CRS.
parameters = Affine.identity(3);
}
final CoordinateSystem normalized = CommonCRS.WGS84.geocentric().getCoordinateSystem();
before = mtFactory.createCoordinateSystemChange(sourceCS, normalized, sourceDatum.getEllipsoid());
after = mtFactory.createCoordinateSystemChange(normalized, targetCS, targetDatum.getEllipsoid());
context.setSource(normalized);
context.setTarget(normalized);
}
} else if (identifier == GEOCENTRIC_CONVERSION) {
parameters = (isGeographicToGeocentric ? GeographicToGeocentric.PARAMETERS : GeocentricToGeographic.PARAMETERS).createValue();
} else {
/*
* Coordinate system change (including change in the number of dimensions) without datum shift.
*/
final int sourceDim = sourceCS.getDimension();
final int targetDim = targetCS.getDimension();
if (// sourceDim == 2 or 3.
(sourceDim & ~1) == 2 && // abs(sourceDim - targetDim) == 1.
(sourceDim ^ targetDim) == 1 && (sourceCS instanceof EllipsoidalCS) && (targetCS instanceof EllipsoidalCS)) {
parameters = (sourceDim == 2 ? Geographic2Dto3D.PARAMETERS : Geographic3Dto2D.PARAMETERS).createValue();
} else {
/*
* TODO: instead than creating parameters for an identity operation, we should create the
* CoordinateOperation directly from the MathTransform created by mtFactory below.
* The intent if to get the correct OperationMethod, which should not be "Affine"
* if there is a CS type change.
*/
parameters = Affine.identity(targetDim);
/*
* createCoordinateSystemChange(…) needs the ellipsoid associated to the ellipsoidal coordinate system,
* if any. If none or both coordinate systems are ellipsoidal, then the ellipsoid will be ignored (see
* createCoordinateSystemChange(…) javadoc for the rational) so it does not matter which one we pick.
*/
before = mtFactory.createCoordinateSystemChange(sourceCS, targetCS, (sourceCS instanceof EllipsoidalCS ? sourceDatum : targetDatum).getEllipsoid());
context.setSource(targetCS);
}
}
/*
* Transform between differents datums using Bursa Wolf parameters. The Bursa Wolf parameters are used
* with "standard" geocentric CS, i.e. with X axis towards the prime meridian, Y axis towards East and
* Z axis toward North, unless the Molodensky approximation is used. The following steps are applied:
*
* source CRS →
* normalized CRS with source datum →
* normalized CRS with target datum →
* target CRS
*
* Those steps may be either explicit with the 'before' and 'after' transform, or implicit with the
* Context parameter.
*/
MathTransform transform = mtFactory.createParameterizedTransform(parameters, context);
final OperationMethod method = mtFactory.getLastMethodUsed();
if (before != null) {
transform = mtFactory.createConcatenatedTransform(before, transform);
if (after != null) {
transform = mtFactory.createConcatenatedTransform(transform, after);
}
}
return asList(createFromMathTransform(properties(identifier), sourceCRS, targetCRS, transform, method, parameters, null));
}
use of org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory in project sis by apache.
the class CoordinateOperationRegistry method fromDefiningConversion.
/**
* Creates a complete coordinate operation from a defining conversion. Defining conversions usually have
* null source and target CRS, but this method nevertheless checks that, in order to reuse the operation
* CRS if it happens to have some.
*
* @param operation the operation specified by the authority.
* @param sourceCRS the source CRS specified by the user.
* @param targetCRS the target CRS specified by the user
* @return a new operation from the given source CRS to target CRS.
* @throws FactoryException if an error occurred while creating the new operation.
*/
private CoordinateOperation fromDefiningConversion(final SingleOperation operation, CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS) throws FactoryException {
final ParameterValueGroup parameters = operation.getParameterValues();
if (parameters != null) {
CoordinateReferenceSystem crs;
if (Utilities.equalsApproximatively(sourceCRS, crs = operation.getSourceCRS()))
sourceCRS = crs;
if (Utilities.equalsApproximatively(targetCRS, crs = operation.getTargetCRS()))
targetCRS = crs;
final MathTransformFactory mtFactory = factorySIS.getMathTransformFactory();
if (mtFactory instanceof DefaultMathTransformFactory) {
MathTransform mt = ((DefaultMathTransformFactory) mtFactory).createParameterizedTransform(parameters, ReferencingUtilities.createTransformContext(sourceCRS, targetCRS, null));
return factorySIS.createSingleOperation(IdentifiedObjects.getProperties(operation), sourceCRS, targetCRS, null, operation.getMethod(), mt);
}
} else {
// Should never happen because parameters are mandatory, but let be safe.
log(Resources.forLocale(null).getLogRecord(Level.WARNING, Resources.Keys.MissingParameterValues_1, IdentifiedObjects.getIdentifierOrName(operation)), null);
}
return null;
}
use of org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory in project sis by apache.
the class CoordinateOperationRegistry method propagateVertical.
/**
* Appends a vertical axis in the source CRS of the first step {@code forward = true} or in
* the target CRS of the last step {@code forward = false} of the given operations chain.
*
* @param source3D the potentially three-dimensional source CRS
* @param target3D the potentially three-dimensional target CRS
* @param operations the chain of operations in which to add a vertical axis.
* @param forward {@code true} for adding the vertical axis at the beginning, or
* {@code false} for adding the vertical axis at the end.
* @return {@code true} on success.
* @throws IllegalArgumentException if the operation method can not have the desired number of dimensions.
*/
private boolean propagateVertical(final CoordinateReferenceSystem source3D, final CoordinateReferenceSystem target3D, final ListIterator<CoordinateOperation> operations, final boolean forward) throws IllegalArgumentException, FactoryException {
while (forward ? operations.hasNext() : operations.hasPrevious()) {
final CoordinateOperation op = forward ? operations.next() : operations.previous();
/*
* We will accept to increase the number of dimensions only for operations between geographic CRS.
* We do not increase the number of dimensions for operations between other kind of CRS because we
* would not know which value to give to the new dimension.
*/
CoordinateReferenceSystem sourceCRS, targetCRS;
if (!((sourceCRS = op.getSourceCRS()) instanceof GeodeticCRS && (targetCRS = op.getTargetCRS()) instanceof GeodeticCRS && sourceCRS.getCoordinateSystem() instanceof EllipsoidalCS && targetCRS.getCoordinateSystem() instanceof EllipsoidalCS)) {
break;
}
/*
* We can process mostly linear operations, otherwise it is hard to know how to add a dimension.
* Examples of linear operations are:
*
* - Longitude rotation (EPSG:9601). Note that this is a transformation rather than a conversion.
* - Geographic3D to 2D conversion (EPSG:9659).
*
* However there is a few special cases where we may be able to add a dimension in a non-linear operation.
* We can attempt those special cases by just giving the same parameters to the math transform factory
* together with the desired CRS. Examples of such special cases are:
*
* - Geocentric translations (geog2D domain)
* - Coordinate Frame Rotation (geog2D domain)
* - Position Vector transformation (geog2D domain)
*/
Matrix matrix = MathTransforms.getMatrix(op.getMathTransform());
if (matrix == null) {
if (SubTypes.isSingleOperation(op)) {
final MathTransformFactory mtFactory = factorySIS.getMathTransformFactory();
if (mtFactory instanceof DefaultMathTransformFactory) {
if (forward)
sourceCRS = toGeodetic3D(sourceCRS, source3D);
else
targetCRS = toGeodetic3D(targetCRS, target3D);
final MathTransform mt;
try {
mt = ((DefaultMathTransformFactory) mtFactory).createParameterizedTransform(((SingleOperation) op).getParameterValues(), ReferencingUtilities.createTransformContext(sourceCRS, targetCRS, null));
} catch (InvalidGeodeticParameterException e) {
log(null, e);
break;
}
operations.set(recreate(op, sourceCRS, targetCRS, mt, mtFactory.getLastMethodUsed()));
return true;
}
}
break;
}
/*
* We can process only one of the following cases:
*
* - Replace a 2D → 2D operation by a 3D → 3D one (i.e. add a passthrough operation).
* - Usually remove (or otherwise edit) the operation that change the number of dimensions
* between the 2D and 3D cases.
*/
final int numRow = matrix.getNumRow();
final int numCol = matrix.getNumCol();
// 2D → 2D operation.
final boolean is2D = (numCol == 3 && numRow == 3);
if (!(is2D || (// 2D → 3D operation.
forward ? // 2D → 3D operation.
(numCol == 3 && numRow == 4) : // 3D → 2D operation.
(numCol == 4 && numRow == 3)))) {
break;
}
matrix = Matrices.resizeAffine(matrix, 4, 4);
if (matrix.isIdentity()) {
operations.remove();
} else {
/*
* If we can not just remove the operation, build a new one with the expected number of dimensions.
* The new operation will inherit the same properties except the identifiers, since it is no longer
* conform to the definition provided by the authority.
*/
final MathTransform mt = factorySIS.getMathTransformFactory().createAffineTransform(matrix);
operations.set(recreate(op, toGeodetic3D(sourceCRS, source3D), toGeodetic3D(targetCRS, target3D), mt, null));
}
/*
* If we processed the operation that change the number of dimensions, we are done.
*/
if (!is2D) {
return true;
}
}
return false;
}
Aggregations