Search in sources :

Example 46 with Identifier

use of org.opengis.metadata.Identifier in project sis by apache.

the class IdentifierCommand method create.

/**
 * Creates an identifier row for the given CRS.
 * This method gives precedence to {@code "urn:ogc:def:"} identifiers if possible.
 *
 * @return the row, or {@code null} if no identifier has been found.
 */
static Row create(ReferenceSystem rs) throws FactoryException {
    String identifier = IdentifiedObjects.lookupURN(rs, null);
    if (identifier == null) {
        /*
             * If we can not find an identifier matching the EPSG or WMS definitions,
             * look at the identifiers declared in the CRS and verify their validity.
             */
        for (final Identifier id : rs.getIdentifiers()) {
            final String c = IdentifiedObjects.toURN(rs.getClass(), id);
            if (c != null) {
                identifier = c;
                // Stop at the first "urn:ogc:def:…".
                break;
            }
            if (identifier == null) {
                // "AUTHORITY:CODE" as a fallback if no URN.
                identifier = IdentifiedObjects.toString(id);
            }
        }
        if (identifier == null) {
            // No identifier found.
            return null;
        }
    }
    /*
         * The CRS provided by the user contains identifier, but the 'lookupURN' operation above failed to
         * find it. The most likely cause is that the user-provided CRS does not use the same axis order.
         */
    State state;
    try {
        final ReferenceSystem def = CRS.forCode(identifier);
        final ComparisonMode c = ComparisonMode.equalityLevel(def, rs);
        if (c == null) {
            state = State.MISMATCH;
        } else
            switch(c) {
                case ALLOW_VARIANT:
                    {
                        state = State.AXIS_ORDER;
                        break;
                    }
                case APPROXIMATIVE:
                    {
                        state = State.APPROXIMATIVE;
                        rs = def;
                        break;
                    }
                default:
                    {
                        state = State.VALID;
                        rs = def;
                        break;
                    }
            }
    } catch (NoSuchAuthorityCodeException e) {
        state = State.UNKNOWN;
    }
    return new Row(state, identifier, rs.getName().getCode());
}
Also used : NoSuchAuthorityCodeException(org.opengis.referencing.NoSuchAuthorityCodeException) Identifier(org.opengis.metadata.Identifier) DefaultIdentifier(org.apache.sis.metadata.iso.DefaultIdentifier) ComparisonMode(org.apache.sis.util.ComparisonMode) ReferenceSystem(org.opengis.referencing.ReferenceSystem)

Example 47 with Identifier

use of org.opengis.metadata.Identifier 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 48 with Identifier

use of org.opengis.metadata.Identifier in project sis by apache.

the class CoordinateOperationFinder method derivedFrom.

/**
 * Returns a name for an object derived from the specified one.
 * This method builds a name of the form "{@literal <original identifier>} (step 1)"
 * where "(step 1)" may be replaced by "(step 2)", "(step 3)", <i>etc.</i> if this
 * method has already been invoked for the same identifier (directly or indirectly).
 */
private Map<String, ?> derivedFrom(final IdentifiedObject object) {
    Identifier oldID = object.getName();
    Object p = identifierOfStepCRS.get(oldID);
    if (p instanceof Identifier) {
        oldID = (Identifier) p;
        p = identifierOfStepCRS.get(oldID);
    }
    final int count = (p != null) ? (Integer) p + 1 : 1;
    final Identifier newID = new NamedIdentifier(Citations.SIS, oldID.getCode() + " (step " + count + ')');
    identifierOfStepCRS.put(newID, oldID);
    identifierOfStepCRS.put(oldID, count);
    final Map<String, Object> properties = new HashMap<>(4);
    properties.put(IdentifiedObject.NAME_KEY, newID);
    properties.put(IdentifiedObject.REMARKS_KEY, Vocabulary.formatInternational(Vocabulary.Keys.DerivedFrom_1, CRSPair.label(object)));
    return properties;
}
Also used : NamedIdentifier(org.apache.sis.referencing.NamedIdentifier) Identifier(org.opengis.metadata.Identifier) HashMap(java.util.HashMap) NamedIdentifier(org.apache.sis.referencing.NamedIdentifier) IdentifiedObject(org.opengis.referencing.IdentifiedObject)

Example 49 with Identifier

use of org.opengis.metadata.Identifier in project sis by apache.

the class GeodeticAuthorityFactory method cast.

/**
 * Casts the given object to the given type, or throws an exception if the object can not be casted.
 * This convenience method is provided for implementation of {@code createXXX} methods.
 *
 * @param  type    the type to return (e.g. {@code CoordinateReferenceSystem.class}).
 * @param  object  the object to cast.
 * @param  code    the authority code, used only for formatting an error message.
 * @return the object casted to the given type.
 * @throws NoSuchAuthorityCodeException if the given object is not an instance of the given type.
 */
@SuppressWarnings("unchecked")
private <T> T cast(final Class<T> type, final IdentifiedObject object, final String code) throws NoSuchAuthorityCodeException {
    if (type.isInstance(object)) {
        return (T) object;
    }
    /*
         * Get the actual type of the object. Returns the GeoAPI type if possible,
         * or fallback on the implementation class otherwise.
         */
    final Class<?> actual;
    if (object instanceof AbstractIdentifiedObject) {
        actual = ((AbstractIdentifiedObject) object).getInterface();
    } else {
        actual = object.getClass();
    }
    /*
         * Get the authority from the object if possible, in order to avoid a call
         * to the potentially costly (for EPSGDataAccess) getAuthority() method.
         */
    final Identifier id = object.getName();
    final Citation authority = (id != null) ? id.getAuthority() : getAuthority();
    throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.UnexpectedTypeForReference_3, code, type, actual), Citations.getIdentifier(authority, false), trimNamespace(code), code);
}
Also used : AbstractIdentifiedObject(org.apache.sis.referencing.AbstractIdentifiedObject) Identifier(org.opengis.metadata.Identifier) Citation(org.opengis.metadata.citation.Citation)

Example 50 with Identifier

use of org.opengis.metadata.Identifier in project sis by apache.

the class DefaultCoordinateSystemAxis method equals.

/**
 * Compares the specified object with this axis for equality.
 * The strictness level is controlled by the second argument.
 * This method compares the following properties in every cases:
 *
 * <ul>
 *   <li>{@link #getName()}</li>
 *   <li>{@link #getDirection()}</li>
 *   <li>{@link #getUnit()}</li>
 * </ul>
 *
 * In the particular case where {@link #getRangeMeaning()} is {@code WRAPAROUND}, then {@link #getMinimumValue()}
 * and {@link #getMaximumValue()} are considered non-ignorable metadata and will be compared for every modes.
 * All other properties are compared only for modes stricter than {@link ComparisonMode#IGNORE_METADATA}.
 *
 * @param  object  the object to compare to {@code this}.
 * @param  mode    {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or
 *                 {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only
 *                 properties relevant to coordinate transformations.
 * @return {@code true} if both objects are equal.
 */
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
    if (object == this) {
        // Slight optimization.
        return true;
    }
    if (!super.equals(object, mode)) {
        return false;
    }
    switch(mode) {
        case STRICT:
            {
                final DefaultCoordinateSystemAxis that = (DefaultCoordinateSystemAxis) object;
                return Objects.equals(unit, that.unit) && Objects.equals(direction, that.direction) && Objects.equals(abbreviation, that.abbreviation) && Objects.equals(rangeMeaning, that.rangeMeaning) && doubleToLongBits(minimumValue) == doubleToLongBits(that.minimumValue) && doubleToLongBits(maximumValue) == doubleToLongBits(that.maximumValue);
            }
        case BY_CONTRACT:
            {
                final CoordinateSystemAxis that = (CoordinateSystemAxis) object;
                return equalsIgnoreMetadata(that, mode, true) && Objects.equals(getAbbreviation(), that.getAbbreviation()) && Objects.equals(getRangeMeaning(), that.getRangeMeaning());
            }
    }
    /*
         * At this point the comparison is in "ignore metadata" mode. We compare the axis range
         * only if the range meaning is "wraparound" for both axes, because only in such case a
         * coordinate operation may shift some ordinate values (typically ±360° on longitudes).
         */
    final CoordinateSystemAxis that = (CoordinateSystemAxis) object;
    if (!equalsIgnoreMetadata(that, mode, RangeMeaning.WRAPAROUND.equals(this.getRangeMeaning()) && RangeMeaning.WRAPAROUND.equals(that.getRangeMeaning()))) {
        return false;
    }
    Identifier name = that.getName();
    if (name != UNNAMED) {
        /*
             * Checking the abbreviation is not sufficient. For example the polar angle and the
             * spherical latitude have the same abbreviation (θ). Legacy names like "Longitude"
             * (in addition to ISO 19111 "Geodetic longitude") bring more potential confusion.
             * Furthermore, not all implementors use the greek letters. For example most CRS in
             * WKT format use the "Lat" abbreviation instead of the greek letter φ.
             * For comparisons without metadata, we ignore the unreliable abbreviation and check
             * the axis name instead. These names are constrained by ISO 19111 specification
             * (see class javadoc), so they should be reliable enough.
             *
             * Note: there is no need to execute this block if metadata are not ignored,
             *       because in this case a stricter check has already been performed by
             *       the 'equals' method in the superclass.
             */
        final String thatCode = name.getCode();
        if (!isHeuristicMatchForName(thatCode)) {
            name = getName();
            if (name != UNNAMED) {
                /*
                     * The above test checked for special cases ("Lat" / "Lon" aliases, etc.).
                     * The next line may repeat the same check, so we may have a partial waste
                     * of CPU.   But we do it anyway for checking the 'that' aliases, and also
                     * because the user may have overridden 'that.isHeuristicMatchForName(…)'.
                     */
                final String thisCode = name.getCode();
                if (!IdentifiedObjects.isHeuristicMatchForName(that, thisCode)) {
                    // Check for the special case of "x" and "y" axis names.
                    if (!isHeuristicMatchForNameXY(thatCode, thisCode) && !isHeuristicMatchForNameXY(thisCode, thatCode)) {
                        return false;
                    }
                }
            }
        }
    }
    return true;
}
Also used : Identifier(org.opengis.metadata.Identifier) CoordinateSystemAxis(org.opengis.referencing.cs.CoordinateSystemAxis) InternationalString(org.opengis.util.InternationalString)

Aggregations

Identifier (org.opengis.metadata.Identifier)60 ReferenceIdentifier (org.opengis.referencing.ReferenceIdentifier)21 Test (org.junit.Test)14 ImmutableIdentifier (org.apache.sis.metadata.iso.ImmutableIdentifier)11 Citation (org.opengis.metadata.citation.Citation)10 IdentifiedObject (org.opengis.referencing.IdentifiedObject)10 InternationalString (org.opengis.util.InternationalString)10 ArrayList (java.util.ArrayList)8 HashMap (java.util.HashMap)6 DefaultIdentifier (org.apache.sis.metadata.iso.DefaultIdentifier)6 IdentifierMap (org.apache.sis.xml.IdentifierMap)6 GeneralParameterDescriptor (org.opengis.parameter.GeneralParameterDescriptor)5 OperationMethod (org.opengis.referencing.operation.OperationMethod)5 NameToIdentifier (org.apache.sis.internal.metadata.NameToIdentifier)4 NamedIdentifier (org.apache.sis.referencing.NamedIdentifier)4 GenericName (org.opengis.util.GenericName)4 URI (java.net.URI)3 ParameterValueGroup (org.opengis.parameter.ParameterValueGroup)3 IdentityHashMap (java.util.IdentityHashMap)2 LinkedHashMap (java.util.LinkedHashMap)2