Search in sources :

Example 1 with DefaultMathTransformFactory

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;
}
Also used : ParameterValueGroup(org.opengis.parameter.ParameterValueGroup) DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) SQLException(java.sql.SQLException) ArrayList(java.util.ArrayList) DefaultCoordinateOperationFactory(org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory) DeferredCoordinateOperation(org.apache.sis.internal.referencing.DeferredCoordinateOperation) InternationalString(org.opengis.util.InternationalString) SimpleInternationalString(org.apache.sis.util.iso.SimpleInternationalString) DefaultOperationMethod(org.apache.sis.referencing.operation.DefaultOperationMethod) ResultSet(java.sql.ResultSet) DefaultCoordinateOperationFactory(org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory) DefaultOperationMethod(org.apache.sis.referencing.operation.DefaultOperationMethod) DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) DeferredCoordinateOperation(org.apache.sis.internal.referencing.DeferredCoordinateOperation) AbstractIdentifiedObject(org.apache.sis.referencing.AbstractIdentifiedObject) IdentifiedObject(org.opengis.referencing.IdentifiedObject)

Example 2 with DefaultMathTransformFactory

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);
}
Also used : DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) NoSuchIdentifierException(org.opengis.util.NoSuchIdentifierException)

Example 3 with DefaultMathTransformFactory

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));
}
Also used : DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) ParameterValueGroup(org.opengis.parameter.ParameterValueGroup) DefaultGeodeticDatum(org.apache.sis.referencing.datum.DefaultGeodeticDatum) NamedIdentifier(org.apache.sis.referencing.NamedIdentifier) Identifier(org.opengis.metadata.Identifier) DefaultGeodeticDatum(org.apache.sis.referencing.datum.DefaultGeodeticDatum) DatumShiftMethod(org.apache.sis.internal.referencing.provider.DatumShiftMethod)

Example 4 with DefaultMathTransformFactory

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;
}
Also used : DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) ParameterValueGroup(org.opengis.parameter.ParameterValueGroup) DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem)

Example 5 with DefaultMathTransformFactory

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;
}
Also used : InvalidGeodeticParameterException(org.apache.sis.referencing.factory.InvalidGeodeticParameterException) DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) DefaultMathTransformFactory(org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory) DeferredCoordinateOperation(org.apache.sis.internal.referencing.DeferredCoordinateOperation) EllipsoidalCS(org.opengis.referencing.cs.EllipsoidalCS) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) GeodeticCRS(org.opengis.referencing.crs.GeodeticCRS)

Aggregations

DefaultMathTransformFactory (org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory)6 ParameterValueGroup (org.opengis.parameter.ParameterValueGroup)4 DeferredCoordinateOperation (org.apache.sis.internal.referencing.DeferredCoordinateOperation)2 CoordinateReferenceSystem (org.opengis.referencing.crs.CoordinateReferenceSystem)2 ResultSet (java.sql.ResultSet)1 SQLException (java.sql.SQLException)1 ArrayList (java.util.ArrayList)1 DatumShiftMethod (org.apache.sis.internal.referencing.provider.DatumShiftMethod)1 AbstractIdentifiedObject (org.apache.sis.referencing.AbstractIdentifiedObject)1 NamedIdentifier (org.apache.sis.referencing.NamedIdentifier)1 DefaultGeodeticDatum (org.apache.sis.referencing.datum.DefaultGeodeticDatum)1 InvalidGeodeticParameterException (org.apache.sis.referencing.factory.InvalidGeodeticParameterException)1 DefaultCoordinateOperationFactory (org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory)1 DefaultOperationMethod (org.apache.sis.referencing.operation.DefaultOperationMethod)1 SimpleInternationalString (org.apache.sis.util.iso.SimpleInternationalString)1 Test (org.junit.Test)1 Identifier (org.opengis.metadata.Identifier)1 IdentifiedObject (org.opengis.referencing.IdentifiedObject)1 GeodeticCRS (org.opengis.referencing.crs.GeodeticCRS)1 EllipsoidalCS (org.opengis.referencing.cs.EllipsoidalCS)1