use of org.opengis.referencing.crs.VerticalCRS in project sis by apache.
the class Extents method getVerticalRange.
/**
* Returns the union of chosen vertical ranges found in the given extent, or {@code null} if none.
* This method gives preference to heights above the Mean Sea Level when possible.
* Depths have negative height values: if the
* {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getDirection() axis direction}
* is toward down, then this method reverses the sign of minimum and maximum values.
*
* <div class="section">Multi-occurrences</div>
* If the given {@code Extent} object contains more than one vertical extent, then this method
* performs a choice based on the vertical datum and the unit of measurement:
*
* <ul class="verbose">
* <li><p><b>Choice based on vertical datum</b><br>
* Only the extents associated (indirectly, through their CRS) to the same non-null {@link VerticalDatumType}
* will be taken in account. If all datum types are null, then this method conservatively uses only the first
* vertical extent. Otherwise the datum type used for filtering the vertical extents is:</p>
*
* <ul>
* <li>{@link VerticalDatumType#GEOIDAL} or {@link VerticalDatumType#DEPTH DEPTH} if at least one extent
* uses those datum types. For this method, {@code DEPTH} is considered as equivalent to {@code GEOIDAL}
* except for the axis direction.</li>
* <li>Otherwise, the first non-null datum type found in iteration order.</li>
* </ul>
*
* <div class="note"><b>Rational:</b> like {@linkplain #getGeographicBoundingBox(Extent) geographic bounding box},
* the vertical range is an approximative information; the range returned by this method does not carry any
* information about the vertical CRS and this method does not attempt to perform coordinate transformation.
* But this method is more useful if the returned ranges are close to a frequently used surface, like the
* Mean Sea Level. The same simplification is applied in the
* <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#31">{@code VerticalExtent} element of
* Well Known Text (WKT) format</a>, which specifies that <cite>“Vertical extent is an approximate description
* of location; heights are relative to an unspecified mean sea level.”</cite></div></li>
*
* <li><p><b>Choice based on units of measurement</b><br>
* If, after the choice based on the vertical datum described above, there is still more than one vertical
* extent to consider, then the next criterion checks for the units of measurement.</p>
* <ul>
* <li>If no range specify a unit of measurement, return the first range and ignore all others.</li>
* <li>Otherwise take the first range having a unit of measurement. Then:<ul>
* <li>All other ranges having an incompatible unit of measurement will be ignored.</li>
* <li>All other ranges having a compatible unit of measurement will be converted to
* the unit of the first retained range, and their union will be computed.</li>
* </ul></li>
* </ul>
*
* <div class="note"><b>Example:</b>
* Heights or depths are often measured using some pressure units, for example hectopascals (hPa).
* An {@code Extent} could contain two vertical elements: one with the height measurements in hPa,
* and the other element with heights transformed to metres using an empirical formula.
* In such case this method will select the first vertical element on the assumption that it is
* the "main" one that the metadata producer intended to show. Next, this method will search for
* other vertical elements using pressure unit. In our example there is none, but if such elements
* were found, this method would compute their union.</div></li>
* </ul>
*
* @param extent the extent to convert to a vertical measurement range, or {@code null}.
* @return a vertical measurement range created from the given extent, or {@code null} if none.
*
* @since 0.4
*/
public static MeasurementRange<Double> getVerticalRange(final Extent extent) {
MeasurementRange<Double> range = null;
VerticalDatumType selectedType = null;
if (extent != null) {
for (final VerticalExtent element : extent.getVerticalElements()) {
double min = element.getMinimumValue();
double max = element.getMaximumValue();
final VerticalCRS crs = element.getVerticalCRS();
VerticalDatumType type = null;
Unit<?> unit = null;
if (crs != null) {
final VerticalDatum datum = crs.getDatum();
if (datum != null) {
type = datum.getVerticalDatumType();
if (VerticalDatumType.DEPTH.equals(type)) {
type = VerticalDatumType.GEOIDAL;
}
}
final CoordinateSystemAxis axis = crs.getCoordinateSystem().getAxis(0);
unit = axis.getUnit();
if (AxisDirection.DOWN.equals(axis.getDirection())) {
final double tmp = min;
min = -max;
max = -tmp;
}
}
if (range != null) {
/*
* If the new range does not specify any datum type or unit, then we do not know how to
* convert the values before to perform the union operation. Conservatively do nothing.
*/
if (type == null || unit == null) {
continue;
}
/*
* If the new range is not measured relative to the same kind of surface than the previous range,
* then we do not know how to combine those ranges. Do nothing, unless the new range is a Mean Sea
* Level Height in which case we forget all previous ranges and use the new one instead.
*/
if (!type.equals(selectedType)) {
if (!type.equals(VerticalDatumType.GEOIDAL)) {
continue;
}
} else if (selectedType != null) {
/*
* If previous range did not specify any unit, then unconditionally replace it by
* the new range since it provides more information. If both ranges specify units,
* then we will compute the union if we can, or ignore the new range otherwise.
*/
final Unit<?> previous = range.unit();
if (previous != null) {
if (previous.isCompatible(unit)) {
range = (MeasurementRange<Double>) range.union(MeasurementRange.create(min, true, max, true, unit));
}
continue;
}
}
}
range = MeasurementRange.create(min, true, max, true, unit);
selectedType = type;
}
}
return range;
}
use of org.opengis.referencing.crs.VerticalCRS in project sis by apache.
the class EllipsoidalHeightCombinerTest method testProjectedCRS.
/**
* Tests {@link EllipsoidalHeightCombiner#createCompoundCRS EllipsoidalHeightCombiner.createCompoundCRS(…)}
* with a projected CRS.
*
* @throws FactoryException if a CRS can not be created.
*/
@Test
@DependsOnMethod("testGeographicCRS")
public void testProjectedCRS() throws FactoryException {
final EllipsoidalHeightCombiner services = create();
final GeodeticObjectFactory factory = new GeodeticObjectFactory();
final Map<String, String> properties = Collections.singletonMap(CoordinateReferenceSystem.NAME_KEY, "World Mercator (4D)");
final ProjectedCRS horizontal = factory.createProjectedCRS(properties, HardCodedCRS.WGS84, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED);
final ProjectedCRS volumetric = factory.createProjectedCRS(properties, HardCodedCRS.WGS84_3D, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED_3D);
final VerticalCRS vertical = HardCodedCRS.ELLIPSOIDAL_HEIGHT;
final TemporalCRS temporal = HardCodedCRS.TIME;
final VerticalCRS geoidal = HardCodedCRS.GRAVITY_RELATED_HEIGHT;
/*
* createCompoundCRS(…) should not combine ProjectedCRS with non-ellipsoidal height.
*/
CoordinateReferenceSystem compound = services.createCompoundCRS(properties, horizontal, geoidal, temporal);
assertArrayEqualsIgnoreMetadata(new SingleCRS[] { horizontal, geoidal, temporal }, CRS.getSingleComponents(compound).toArray());
/*
* createCompoundCRS(…) should combine ProjectedCRS with ellipsoidal height.
*/
compound = services.createCompoundCRS(properties, horizontal, vertical);
assertArrayEqualsIgnoreMetadata(new SingleCRS[] { volumetric }, CRS.getSingleComponents(compound).toArray());
/*
* createCompoundCRS(…) should combine ProjectedCRS with ellipsoidal height and keep time.
*/
compound = services.createCompoundCRS(properties, horizontal, vertical, temporal);
assertArrayEqualsIgnoreMetadata(new SingleCRS[] { volumetric, temporal }, CRS.getSingleComponents(compound).toArray());
/*
* Non-standard feature: accept (VerticalCRS + ProjectedCRS) order.
*/
compound = services.createCompoundCRS(properties, temporal, vertical, horizontal);
final Object[] components = CRS.getSingleComponents(compound).toArray();
assertEquals(2, components.length);
assertEqualsIgnoreMetadata(temporal, components[0]);
assertInstanceOf("Shall be a three-dimensional projected CRS.", ProjectedCRS.class, components[1]);
assertAxisDirectionsEqual("Shall be a three-dimensional projected CRS.", ((CoordinateReferenceSystem) components[1]).getCoordinateSystem(), AxisDirection.UP, AxisDirection.EAST, AxisDirection.NORTH);
}
use of org.opengis.referencing.crs.VerticalCRS in project sis by apache.
the class WKTFormatTest method testParse.
/**
* Tests integration in {@link WKTFormat#parse(CharSequence, ParsePosition)}.
* This method tests only a simple WKT because it is not the purpose of this
* method to test the parser itself. We only want to tests its integration in
* the {@link WKTFormat} class.
*
* @throws ParseException if the parsing failed.
*/
@Test
public void testParse() throws ParseException {
format = new WKTFormat(null, null);
final VerticalCRS crs = (VerticalCRS) format.parseObject("VERT_CS[“Gravity-related height”,\n" + " VERT_DATUM[“Mean Sea Level”, 2005],\n" + " UNIT[“metre”, 1],\n" + " AXIS[“Gravity-related height”, UP]]");
GeodeticObjectParserTest.assertNameAndIdentifierEqual("Gravity-related height", 0, crs);
GeodeticObjectParserTest.assertNameAndIdentifierEqual("Mean Sea Level", 0, crs.getDatum());
}
use of org.opengis.referencing.crs.VerticalCRS in project sis by apache.
the class DefaultCompoundCRS method verify.
/**
* Verifies that the given array does not contain duplicated horizontal or vertical components.
* Verifies also that if there is an horizontal component, then there is no ellipsoidal height
* defined separately.
*
* @param properties the user-specified properties, for determining the locale of error messages.
* @param components the components to verify.
*/
private static void verify(final Map<String, ?> properties, final CoordinateReferenceSystem[] components) {
int allTypes = 0;
// 0 for false, 1 for true.
int isProjected = 0;
boolean isEllipsoidalHeight = false;
for (final CoordinateReferenceSystem component : components) {
final int type;
if (component instanceof GeodeticCRS) {
// Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
type = 1;
} else if (component instanceof ProjectedCRS) {
isProjected = 1;
// Intentionally same number than for GeographicCRS case.
type = 1;
} else if (component instanceof VerticalCRS) {
isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(((VerticalCRS) component).getDatum());
// Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
type = 2;
} else {
// Skip other types. In particular, we allow 2 temporal CRS (used in meteorology).
continue;
}
if (allTypes == (allTypes |= type)) {
throw new IllegalArgumentException(Resources.forProperties(properties).getString(Resources.Keys.DuplicatedSpatialComponents_1, type));
}
}
if (isEllipsoidalHeight && ((allTypes & 1) != 0)) {
throw new IllegalArgumentException(Resources.forProperties(properties).getString(Resources.Keys.EllipsoidalHeightNotAllowed_1, isProjected));
}
}
use of org.opengis.referencing.crs.VerticalCRS in project sis by apache.
the class CoordinateOperationFinderTest method testGeographic4D_to_EllipsoidalHeight.
/**
* Tests extracting the vertical part of a spatio-temporal CRS.
*
* @throws FactoryException if the operation can not be created.
* @throws TransformException if an error occurred while converting the test points.
*/
@Test
@DependsOnMethod("testGeographic3D_to_EllipsoidalHeight")
public void testGeographic4D_to_EllipsoidalHeight() throws FactoryException, TransformException {
// NOTE: make sure that the 'sourceCRS' below is not equal to any other 'sourceCRS' created in this class.
final CompoundCRS sourceCRS = compound("Test4D", CommonCRS.WGS84.geographic3D(), CommonCRS.Temporal.JULIAN.crs());
final VerticalCRS targetCRS = CommonCRS.Vertical.ELLIPSOIDAL.crs();
final CoordinateOperation operation = finder.createOperation(sourceCRS, targetCRS);
assertSame("sourceCRS", sourceCRS, operation.getSourceCRS());
assertSame("targetCRS", targetCRS, operation.getTargetCRS());
assertEquals("name", "Axis changes", operation.getName().getCode());
assertInstanceOf("operation", Conversion.class, operation);
transform = operation.getMathTransform();
assertInstanceOf("transform", LinearTransform.class, transform);
assertEquals("sourceDimensions", 4, transform.getSourceDimensions());
assertEquals("targetDimensions", 1, transform.getTargetDimensions());
Assert.assertMatrixEquals("transform.matrix", Matrices.create(2, 5, new double[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 }), ((LinearTransform) transform).getMatrix(), STRICT);
isInverseTransformSupported = false;
verifyTransform(new double[] { 0, 0, 0, 0, 5, 8, 20, 10, -5, -8, 24, 30 }, new double[] { 0, 20, 24 });
validate();
}
Aggregations