use of org.opengis.referencing.cs.EllipsoidalCS in project sis by apache.
the class GeocentricAffine method createParameters.
/**
* Returns the parameters for creating a datum shift operation.
* The operation method will be one of the {@code GeocentricAffine} subclasses,
* unless the specified {@code method} argument is {@link DatumShiftMethod#NONE}.
* If no single operation method can be used, then this method returns {@code null}.
*
* <p>This method does <strong>not</strong> change the coordinate system type.
* The source and target coordinate systems can be both {@code EllipsoidalCS} or both {@code CartesianCS}.
* Any other type or mix of types (e.g. a {@code EllipsoidalCS} source and {@code CartesianCS} target)
* will cause this method to return {@code null}. In such case, it is caller's responsibility to apply
* the datum shift itself in Cartesian geocentric coordinates.</p>
*
* @param sourceCS the source coordinate system. Only the type and number of dimensions is checked.
* @param targetCS the target coordinate system. Only the type and number of dimensions is checked.
* @param datumShift the datum shift as a matrix, or {@code null} if there is no datum shift information.
* @param method the preferred datum shift method. Note that {@code createParameters(…)} may overwrite.
* @return the parameter values, or {@code null} if no single operation method can be found.
*/
public static ParameterValueGroup createParameters(final CoordinateSystem sourceCS, final CoordinateSystem targetCS, final Matrix datumShift, DatumShiftMethod method) {
final boolean isEllipsoidal = (sourceCS instanceof EllipsoidalCS);
if (!(isEllipsoidal ? (targetCS instanceof EllipsoidalCS) : (targetCS instanceof CartesianCS && sourceCS instanceof CartesianCS))) {
// Coordinate systems are not two EllipsoidalCS or two CartesianCS.
return null;
}
@SuppressWarnings("null") int dimension = sourceCS.getDimension();
if (dimension != targetCS.getDimension()) {
// Any value greater than 3 means "mismatched dimensions" for this method.
dimension = 4;
}
if (method == DatumShiftMethod.NONE) {
if (dimension <= 3) {
return Affine.identity(dimension);
} else if (isEllipsoidal) {
final ParameterDescriptorGroup descriptor;
switch(sourceCS.getDimension()) {
case 2:
descriptor = Geographic2Dto3D.PARAMETERS;
break;
case 3:
descriptor = Geographic3Dto2D.PARAMETERS;
break;
default:
return null;
}
return descriptor.createValue();
} else {
return null;
}
}
/*
* Try to convert the matrix into (tX, tY, tZ, rX, rY, rZ, dS) parameters.
* The matrix may not be convertible, in which case we will let the caller
* uses the matrix directly in Cartesian geocentric coordinates.
*/
final BursaWolfParameters parameters = new BursaWolfParameters(null, null);
if (datumShift != null)
try {
parameters.setPositionVectorTransformation(datumShift, BURSAWOLF_TOLERANCE);
} catch (IllegalArgumentException e) {
log(Loggers.COORDINATE_OPERATION, "createParameters", e);
return null;
}
else {
/*
* If there is no datum shift parameters (not to be confused with identity), then those parameters
* are assumed unknown. Using the most accurate methods would give a false impression of accuracy,
* so we use the fastest method instead. Since all parameter values are zero, Apache SIS should use
* the AbridgedMolodenskyTransform2D optimization.
*/
method = DatumShiftMethod.ABRIDGED_MOLODENSKY;
}
final boolean isTranslation = parameters.isTranslation();
final ParameterDescriptorGroup descriptor;
/*
* Following "if" blocks are ordered from most accurate to less accurate datum shift method
* supported by GeocentricAffine subclasses (except NONE which has already been handled).
* Special cases:
*
* - If the datum shift is applied between geocentric CRS, then the Molodensky approximations do not apply
* as they are designed for transformations between geographic CRS only. User preference is then ignored.
*
* - Molodensky methods are approximations for datum shifts having only translation terms in their Bursa-Wolf
* parameters. If there is also a scale or rotation terms, then we can not use Molodensky methods. The user
* preference is then ignored.
*/
if (!isEllipsoidal) {
method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
descriptor = isTranslation ? GeocentricTranslation.PARAMETERS : PositionVector7Param.PARAMETERS;
} else if (!isTranslation) {
method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
descriptor = (dimension >= 3) ? PositionVector7Param3D.PARAMETERS : PositionVector7Param2D.PARAMETERS;
} else
switch(method) {
case GEOCENTRIC_DOMAIN:
{
descriptor = (dimension >= 3) ? GeocentricTranslation3D.PARAMETERS : GeocentricTranslation2D.PARAMETERS;
break;
}
case MOLODENSKY:
{
descriptor = Molodensky.PARAMETERS;
break;
}
case ABRIDGED_MOLODENSKY:
{
descriptor = AbridgedMolodensky.PARAMETERS;
break;
}
default:
throw new AssertionError(method);
}
/*
* Following lines will set all Bursa-Wolf parameter values (scale, translation
* and rotation terms). In the particular case of Molodensky method, we have an
* additional parameter for the number of source and target dimensions (2 or 3).
*/
final Parameters values = createParameters(descriptor, parameters, isTranslation);
switch(method) {
case MOLODENSKY:
case ABRIDGED_MOLODENSKY:
{
if (dimension <= 3) {
values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
}
break;
}
}
return values;
}
use of org.opengis.referencing.cs.EllipsoidalCS in project sis by apache.
the class MathTransformContext method getMatrix.
/**
* Returns the normalization or denormalization matrix.
*/
@Override
@SuppressWarnings("fallthrough")
public Matrix getMatrix(final MatrixRole role) throws FactoryException {
final CoordinateSystem cs;
boolean inverse = false;
double rotation;
switch(role) {
default:
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "role", role));
// Fall through
case INVERSE_NORMALIZATION:
inverse = true;
case NORMALIZATION:
rotation = sourceMeridian;
cs = getSourceCS();
break;
// Fall through
case INVERSE_DENORMALIZATION:
inverse = true;
case DENORMALIZATION:
inverse = !inverse;
rotation = targetMeridian;
cs = getTargetCS();
break;
}
Matrix matrix = super.getMatrix(role);
if (rotation != 0) {
if (inverse)
rotation = -rotation;
MatrixSIS cm = MatrixSIS.castOrCopy(matrix);
if (cs instanceof CartesianCS) {
rotation = Math.toRadians(rotation);
final Matrix4 rot = new Matrix4();
rot.m00 = rot.m11 = Math.cos(rotation);
rot.m01 = -(rot.m10 = Math.sin(rotation));
if (inverse) {
// Apply the rotation after denormalization.
matrix = Matrices.multiply(rot, cm);
} else {
// Apply the rotation before normalization.
matrix = cm.multiply(rot);
}
} else if (cs == null || cs instanceof EllipsoidalCS || cs instanceof SphericalCS) {
final Double value = rotation;
if (inverse) {
// Longitude is the first axis in normalized CS.
cm.convertBefore(0, null, value);
} else {
cm.convertAfter(0, null, value);
}
matrix = cm;
} else {
throw new FactoryException(Errors.format(Errors.Keys.UnsupportedCoordinateSystem_1, cs.getName()));
}
}
return matrix;
}
use of org.opengis.referencing.cs.EllipsoidalCS in project sis by apache.
the class CommonCRSTest method testGeographic3D.
/**
* Tests the {@link CommonCRS#geographic3D()} method.
*/
@Test
@DependsOnMethod("testGeographic")
public void testGeographic3D() {
final GeographicCRS crs = CommonCRS.WGS72.geographic3D();
Validators.validate(crs);
assertEquals("WGS 72", crs.getName().getCode());
assertSame(CommonCRS.WGS72.geographic().getDatum(), crs.getDatum());
assertNotSame(CommonCRS.WGS84.geographic().getDatum(), crs.getDatum());
final EllipsoidalCS cs = crs.getCoordinateSystem();
final String name = cs.getName().getCode();
assertTrue(name, name.startsWith("Ellipsoidal 3D"));
assertEquals("dimension", 3, cs.getDimension());
assertAxisDirectionsEqual(name, cs, AxisDirection.NORTH, AxisDirection.EAST, AxisDirection.UP);
assertSame("Cached value", crs, CommonCRS.WGS72.geographic3D());
}
use of org.opengis.referencing.cs.EllipsoidalCS in project sis by apache.
the class DefaultGeographicCRSTest method testConventionalOrientation.
/**
* Tests the {@link DefaultGeographicCRS#forConvention(AxesConvention)} method
* for {@link AxesConvention#CONVENTIONALLY_ORIENTED}.
*/
@Test
public void testConventionalOrientation() {
final DefaultGeographicCRS crs = DefaultGeographicCRS.castOrCopy(CommonCRS.WGS84.geographic3D());
final DefaultGeographicCRS normalized = crs.forConvention(AxesConvention.CONVENTIONALLY_ORIENTED);
assertNotSame(crs, normalized);
final EllipsoidalCS cs = normalized.getCoordinateSystem();
final EllipsoidalCS ref = crs.getCoordinateSystem();
assertSame("longitude", ref.getAxis(1), cs.getAxis(0));
assertSame("latitude", ref.getAxis(0), cs.getAxis(1));
assertSame("height", ref.getAxis(2), cs.getAxis(2));
}
use of org.opengis.referencing.cs.EllipsoidalCS in project sis by apache.
the class DefaultGeodeticCRS method formatTo.
/**
* Formats this CRS as a <cite>Well Known Text</cite> {@code GeodeticCRS[…]} element.
* More information about the WKT format is documented in subclasses.
*
* @return {@code "GeodeticCRS"} (WKT 2) or {@code "GeogCS"}/{@code "GeocCS"} (WKT 1).
*/
@Override
protected String formatTo(final Formatter formatter) {
WKTUtilities.appendName(this, formatter, null);
CoordinateSystem cs = getCoordinateSystem();
final Convention convention = formatter.getConvention();
final boolean isWKT1 = (convention.majorVersion() == 1);
final boolean isGeographicWKT1 = isWKT1 && (cs instanceof EllipsoidalCS);
if (isGeographicWKT1 && cs.getDimension() == 3) {
/*
* Version 1 of WKT format did not have three-dimensional GeographicCRS. Instead, such CRS were formatted
* as a CompoundCRS made of a two-dimensional GeographicCRS with a VerticalCRS for the ellipsoidal height.
* Note that such compound is illegal in WKT 2 and ISO 19111 standard, as ellipsoidal height shall not be
* separated from the geographic component. So we perform this separation only at WKT 1 formatting time.
*/
SingleCRS first = CRS.getHorizontalComponent(this);
SingleCRS second = CRS.getVerticalComponent(this, true);
if (first != null && second != null) {
// Should not be null, but we are paranoiac.
if (AxisDirection.UP.equals(AxisDirections.absolute(cs.getAxis(0).getDirection()))) {
// It is very unusual to have VerticalCRS first, but our code tries to be robust.
final SingleCRS t = first;
first = second;
second = t;
}
formatter.newLine();
formatter.append(WKTUtilities.toFormattable(first));
formatter.newLine();
formatter.append(WKTUtilities.toFormattable(second));
formatter.newLine();
return WKTKeywords.Compd_CS;
}
}
/*
* Unconditionally format the datum element, followed by the prime meridian.
* The prime meridian is part of datum according ISO 19111, but is formatted
* as a sibling (rather than a child) element in WKT for historical reasons.
*/
// Gives subclasses a chance to override.
final GeodeticDatum datum = getDatum();
formatter.newLine();
formatter.append(WKTUtilities.toFormattable(datum));
formatter.newLine();
final PrimeMeridian pm = datum.getPrimeMeridian();
final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, null);
if (// Really this specific enum, not Convention.isSimplified().
convention != Convention.WKT2_SIMPLIFIED || ReferencingUtilities.getGreenwichLongitude(pm, Units.DEGREE) != 0) {
final Unit<Angle> oldUnit = formatter.addContextualUnit(angularUnit);
formatter.indent(1);
formatter.append(WKTUtilities.toFormattable(pm));
formatter.indent(-1);
formatter.newLine();
formatter.restoreContextualUnit(angularUnit, oldUnit);
}
/*
* Get the coordinate system to format. This will also determine the units to write and the keyword to
* return in WKT 1 format. Note that for the WKT 1 format, we need to replace the coordinate system by
* an instance conform to the legacy conventions.
*
* We can not delegate the work below to subclasses, because XML unmarshalling of a geodetic CRS will
* NOT create an instance of a subclass (because the distinction between geographic and geocentric CRS
* is not anymore in ISO 19111:2007).
*/
final boolean isBaseCRS;
if (isWKT1) {
if (!isGeographicWKT1) {
// If not geographic, then presumed geocentric.
if (cs instanceof CartesianCS) {
cs = Legacy.forGeocentricCRS((CartesianCS) cs, true);
} else {
// SphericalCS was not supported in WKT 1.
formatter.setInvalidWKT(cs, null);
}
}
isBaseCRS = false;
} else {
isBaseCRS = isBaseCRS(formatter);
}
/*
* Format the coordinate system, except if this CRS is the base CRS of an AbstractDerivedCRS in WKT 2 format.
* This is because ISO 19162 omits the coordinate system definition of enclosed base CRS in order to simplify
* the WKT. The 'formatCS(…)' method may write axis unit before or after the axes depending on whether we are
* formatting WKT version 1 or 2 respectively.
*
* Note that even if we do not format the CS, we may still write the units if we are formatting in "simplified"
* mode (as opposed to the more verbose mode). This looks like the opposite of what we would expect, but this is
* because formatting the unit here allow us to avoid repeating the unit in projection parameters when this CRS
* is part of a ProjectedCRS. Note however that in such case, the units to format are the angular units because
* the linear units will be formatted in the enclosing PROJCS[…] element.
*/
if (!isBaseCRS || convention == Convention.INTERNAL) {
// Will also format the axes unit.
formatCS(formatter, cs, ReferencingUtilities.getUnit(cs), isWKT1);
} else if (convention.isSimplified()) {
formatter.append(formatter.toContextualUnit(angularUnit));
}
/*
* For WKT 1, the keyword depends on the subclass: "GeogCS" for GeographicCRS or "GeocCS" for GeocentricCRS.
* However we can not rely on the subclass for choosing the keyword, because after XML unmarhaling we only
* have a GeodeticCRS. We need to make the choice in this base class. The CS type is a sufficient criterion.
*/
if (isWKT1) {
return isGeographicWKT1 ? WKTKeywords.GeogCS : WKTKeywords.GeocCS;
} else {
return isBaseCRS ? WKTKeywords.BaseGeodCRS : formatter.shortOrLong(WKTKeywords.GeodCRS, WKTKeywords.GeodeticCRS);
}
}
Aggregations