use of org.opengis.referencing.cs.CoordinateSystemAxis in project sis by apache.
the class Envelopes method transform.
/**
* Transforms an envelope using the given coordinate operation.
* The transformation is only approximative: the returned envelope may be bigger than the
* smallest possible bounding box, but should not be smaller in most cases.
*
* <p>This method can handle the case where the envelope contains the North or South pole,
* or when it cross the ±180° longitude.</p>
*
* <div class="note"><b>Note:</b>
* If the envelope CRS is non-null, then the caller should ensure that the operation source CRS
* is the same than the envelope CRS. In case of mismatch, this method transforms the envelope
* to the operation source CRS before to apply the operation. This extra step may cause a lost
* of accuracy. In order to prevent this method from performing such pre-transformation (if not desired),
* callers can ensure that the envelope CRS is {@code null} before to call this method.</div>
*
* @param operation the operation to use.
* @param envelope envelope to transform, or {@code null}. This envelope will not be modified.
* @return the transformed envelope, or {@code null} if {@code envelope} was null.
* @throws TransformException if a transform failed.
*
* @see #transform(MathTransform, Envelope)
*
* @since 0.5
*/
@SuppressWarnings("null")
public static GeneralEnvelope transform(final CoordinateOperation operation, Envelope envelope) throws TransformException {
ensureNonNull("operation", operation);
if (envelope == null) {
return null;
}
boolean isOperationComplete = true;
final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS();
if (sourceCRS != null) {
final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
if (crs != null && !Utilities.equalsIgnoreMetadata(crs, sourceCRS)) {
/*
* Argument-check: the envelope CRS seems inconsistent with the given operation.
* However we need to push the check a little bit further, since 3D-GeographicCRS
* are considered not equal to CompoundCRS[2D-GeographicCRS + ellipsoidal height].
* Checking for identity MathTransform is a more powerfull (but more costly) check.
* Since we have the MathTransform, perform an opportunist envelope transform if it
* happen to be required.
*/
final MathTransform mt;
try {
mt = CoordinateOperations.factory().createOperation(crs, sourceCRS).getMathTransform();
} catch (FactoryException e) {
throw new TransformException(Errors.format(Errors.Keys.CanNotTransformEnvelope), e);
}
if (!mt.isIdentity()) {
isOperationComplete = false;
envelope = transform(mt, envelope);
}
}
}
MathTransform mt = operation.getMathTransform();
final double[] centerPt = new double[mt.getTargetDimensions()];
final GeneralEnvelope transformed = transform(mt, envelope, centerPt);
/*
* If the source envelope crosses the expected range of valid coordinates, also projects
* the range bounds as a safety. Example: if the source envelope goes from 150 to 200°E,
* some map projections will interpret 200° as if it was -160°, and consequently produce
* an envelope which do not include the 180°W extremum. We will add those extremum points
* explicitly as a safety. It may leads to bigger than necessary target envelope, but the
* contract is to include at least the source envelope, not to return the smallest one.
*/
if (sourceCRS != null) {
final CoordinateSystem cs = sourceCRS.getCoordinateSystem();
if (cs != null) {
// Should never be null, but check as a paranoiac safety.
DirectPosition sourcePt = null;
DirectPosition targetPt = null;
final int dimension = cs.getDimension();
for (int i = 0; i < dimension; i++) {
final CoordinateSystemAxis axis = cs.getAxis(i);
if (axis == null) {
// Should never be null, but check as a paranoiac safety.
continue;
}
final double min = envelope.getMinimum(i);
final double max = envelope.getMaximum(i);
final double v1 = axis.getMinimumValue();
final double v2 = axis.getMaximumValue();
final boolean b1 = (v1 > min && v1 < max);
final boolean b2 = (v2 > min && v2 < max);
if (!b1 && !b2) {
continue;
}
if (sourcePt == null) {
sourcePt = new GeneralDirectPosition(dimension);
for (int j = 0; j < dimension; j++) {
sourcePt.setOrdinate(j, envelope.getMedian(j));
}
}
if (b1) {
sourcePt.setOrdinate(i, v1);
transformed.add(targetPt = mt.transform(sourcePt, targetPt));
}
if (b2) {
sourcePt.setOrdinate(i, v2);
transformed.add(targetPt = mt.transform(sourcePt, targetPt));
}
sourcePt.setOrdinate(i, envelope.getMedian(i));
}
}
}
/*
* Now takes the target CRS in account...
*/
final CoordinateReferenceSystem targetCRS = operation.getTargetCRS();
if (targetCRS == null) {
return transformed;
}
transformed.setCoordinateReferenceSystem(targetCRS);
final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
if (targetCS == null) {
// It should be an error, but we keep this method tolerant.
return transformed;
}
/*
* Checks for singularity points. For example the south pole is a singularity point in
* geographic CRS because is is located at the maximal value allowed by one particular
* axis, namely latitude. This point is not a singularity in the stereographic projection,
* because axes extends toward infinity in all directions (mathematically) and because the
* South pole has nothing special apart being the origin (0,0).
*
* Algorithm:
*
* 1) Inspect the target axis, looking if there is any bounds. If bounds are found, get
* the coordinates of singularity points and project them from target to source CRS.
*
* Example: If the transformed envelope above is (80 … 85°S, 10 … 50°W), and if the
* latitude in the target CRS is bounded at 90°S, then project (90°S, 30°W)
* to the source CRS. Note that the longitude is set to the the center of
* the envelope longitude range (more on this below).
*
* 2) If the singularity point computed above is inside the source envelope, add that
* point to the target (transformed) envelope.
*
* 3) If step #2 added the point, iterate over all other axes. If an other bounded axis
* is found and that axis is of kind "WRAPAROUND", test for inclusion the same point
* than the point tested at step #1, except for the ordinate of the axis found in this
* step. That ordinate is set to the minimal and maximal values of that axis.
*
* Example: If the above steps found that the point (90°S, 30°W) need to be included,
* then this step #3 will also test the points (90°S, 180°W) and (90°S, 180°E).
*
* NOTE: we test (-180°, centerY), (180°, centerY), (centerX, -90°) and (centerX, 90°)
* at step #1 before to test (-180°, -90°), (180°, -90°), (-180°, 90°) and (180°, 90°)
* at step #3 because the later may not be supported by every projections. For example
* if the target envelope is located between 20°N and 40°N, then a Mercator projection
* may fail to transform the (-180°, 90°) coordinate while the (-180°, 30°) coordinate
* is a valid point.
*/
TransformException warning = null;
AbstractEnvelope generalEnvelope = null;
DirectPosition sourcePt = null;
DirectPosition targetPt = null;
// A bitmask for each dimension.
long includedMinValue = 0;
long includedMaxValue = 0;
long isWrapAroundAxis = 0;
long dimensionBitMask = 1;
final int dimension = targetCS.getDimension();
poles: for (int i = 0; i < dimension; i++, dimensionBitMask <<= 1) {
final CoordinateSystemAxis axis = targetCS.getAxis(i);
if (axis == null) {
// Should never be null, but check as a paranoiac safety.
continue;
}
// Tells if we are testing the minimal or maximal value.
boolean testMax = false;
do {
final double extremum = testMax ? axis.getMaximumValue() : axis.getMinimumValue();
if (Double.isInfinite(extremum) || Double.isNaN(extremum)) {
/*
* The axis is unbounded. It should always be the case when the target CRS is
* a map projection, in which case this loop will finish soon and this method
* will do nothing more (no object instantiated, no MathTransform inversed...)
*/
continue;
}
if (targetPt == null) {
try {
mt = mt.inverse();
} catch (NoninvertibleTransformException exception) {
/*
* If the transform is non invertible, this method can't do anything. This
* is not a fatal error because the envelope has already be transformed by
* the caller. We lost the check for singularity points performed by this
* method, but it make no difference in the common case where the source
* envelope didn't contains any of those points.
*
* Note that this exception is normal if target dimension is smaller than
* source dimension, since the math transform can not reconstituate the
* lost dimensions. So we don't log any warning in this case.
*/
if (dimension >= mt.getSourceDimensions()) {
warning = exception;
}
break poles;
}
targetPt = new GeneralDirectPosition(mt.getSourceDimensions());
for (int j = 0; j < dimension; j++) {
targetPt.setOrdinate(j, centerPt[j]);
}
// TODO: avoid the hack below if we provide a contains(DirectPosition)
// method in the GeoAPI org.opengis.geometry.Envelope interface.
generalEnvelope = AbstractEnvelope.castOrCopy(envelope);
}
targetPt.setOrdinate(i, extremum);
try {
sourcePt = mt.transform(targetPt, sourcePt);
} catch (TransformException exception) {
/*
* This exception may be normal. For example if may occur when projecting
* the latitude extremums with a cylindrical Mercator projection. Do not
* log any message (unless logging level is fine) and try the other points.
*/
if (warning == null) {
warning = exception;
} else {
warning.addSuppressed(exception);
}
continue;
}
if (generalEnvelope.contains(sourcePt)) {
transformed.add(targetPt);
if (testMax)
includedMaxValue |= dimensionBitMask;
else
includedMinValue |= dimensionBitMask;
}
} while ((testMax = !testMax) == true);
/*
* Keep trace of axes of kind WRAPAROUND, except if the two extremum values of that
* axis have been included in the envelope (in which case the next step after this
* loop doesn't need to be executed for that axis).
*/
if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 && CoordinateOperations.isWrapAround(axis)) {
isWrapAroundAxis |= dimensionBitMask;
}
// Restore 'targetPt' to its initial state, which is equals to 'centerPt'.
if (targetPt != null) {
targetPt.setOrdinate(i, centerPt[i]);
}
}
/*
* Step #3 described in the above "Algorithm" section: iterate over all dimensions
* of type "WRAPAROUND" for which minimal or maximal axis values have not yet been
* included in the envelope. The set of axes is specified by a bitmask computed in
* the above loop. We examine only the points that have not already been included
* in the envelope.
*/
final long includedBoundsValue = (includedMinValue | includedMaxValue);
if (includedBoundsValue != 0) {
while (isWrapAroundAxis != 0) {
final int wrapAroundDimension = Long.numberOfTrailingZeros(isWrapAroundAxis);
dimensionBitMask = 1 << wrapAroundDimension;
// Clear now the bit, for the next iteration.
isWrapAroundAxis &= ~dimensionBitMask;
final CoordinateSystemAxis wrapAroundAxis = targetCS.getAxis(wrapAroundDimension);
final double min = wrapAroundAxis.getMinimumValue();
final double max = wrapAroundAxis.getMaximumValue();
/*
* Iterate over all axes for which a singularity point has been previously found,
* excluding the "wrap around axis" currently under consideration.
*/
for (long am = (includedBoundsValue & ~dimensionBitMask), bm; am != 0; am &= ~bm) {
bm = Long.lowestOneBit(am);
final int axisIndex = Long.numberOfTrailingZeros(bm);
final CoordinateSystemAxis axis = targetCS.getAxis(axisIndex);
/*
* switch (c) {
* case 0: targetPt = (..., singularityMin, ..., wrapAroundMin, ...)
* case 1: targetPt = (..., singularityMin, ..., wrapAroundMax, ...)
* case 2: targetPt = (..., singularityMax, ..., wrapAroundMin, ...)
* case 3: targetPt = (..., singularityMax, ..., wrapAroundMax, ...)
* }
*/
for (int c = 0; c < 4; c++) {
/*
* Set the ordinate value along the axis having the singularity point
* (cases c=0 and c=2). If the envelope did not included that point,
* then skip completely this case and the next one, i.e. skip c={0,1}
* or skip c={2,3}.
*/
double value = max;
if ((c & 1) == 0) {
// 'true' if we are testing "wrapAroundMin".
if (((c == 0 ? includedMinValue : includedMaxValue) & bm) == 0) {
// Skip also the case for "wrapAroundMax".
c++;
continue;
}
targetPt.setOrdinate(axisIndex, (c == 0) ? axis.getMinimumValue() : axis.getMaximumValue());
value = min;
}
targetPt.setOrdinate(wrapAroundDimension, value);
try {
sourcePt = mt.transform(targetPt, sourcePt);
} catch (TransformException exception) {
if (warning == null) {
warning = exception;
} else {
warning.addSuppressed(exception);
}
continue;
}
if (generalEnvelope.contains(sourcePt)) {
transformed.add(targetPt);
}
}
targetPt.setOrdinate(axisIndex, centerPt[axisIndex]);
}
targetPt.setOrdinate(wrapAroundDimension, centerPt[wrapAroundDimension]);
}
}
/*
* At this point we finished envelope transformation. Verify if some ordinates need to be "wrapped around"
* as a result of the coordinate operation. This is usually the longitude axis where the source CRS uses
* the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. We do not wrap
* around if the source and target axes use the same range (e.g. the longitude stay [-180 … +180]°) in order
* to reduce the risk of discontinuities. If the user really wants unconditional wrap around, (s)he can call
* GeneralEnvelope.normalize().
*/
final Set<Integer> wrapAroundChanges;
if (isOperationComplete && operation instanceof AbstractCoordinateOperation) {
wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
} else {
wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
}
transformed.normalize(targetCS, 0, wrapAroundChanges.size(), wrapAroundChanges.iterator());
if (warning != null) {
recoverableException(Envelopes.class, warning);
}
return transformed;
}
use of org.opengis.referencing.cs.CoordinateSystemAxis in project sis by apache.
the class Formatter method append.
/**
* Appends a unit in a {@code Unit[…]} element or one of the specialized elements. Specialized elements are
* {@code AngleUnit}, {@code LengthUnit}, {@code ScaleUnit}, {@code ParametricUnit} and {@code TimeUnit}.
* By {@linkplain KeywordStyle#DEFAULT default}, specialized unit keywords are used with the
* {@linkplain Convention#WKT2 WKT 2 convention}.
*
* <div class="note"><b>Example:</b>
* {@code append(Units.KILOMETRE)} will append "{@code LengthUnit["km", 1000]}" to the WKT.</div>
*
* @param unit the unit to append to the WKT, or {@code null} if none.
*
* @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#35">WKT 2 specification §7.4</a>
*/
public void append(final Unit<?> unit) {
if (unit != null) {
final boolean isSimplified = (longKeywords == 0) ? convention.isSimplified() : (longKeywords < 0);
final boolean isWKT1 = convention.majorVersion() == 1;
final Unit<?> base = unit.getSystemUnit();
final String keyword;
if (base.equals(Units.METRE)) {
keyword = isSimplified ? WKTKeywords.Unit : WKTKeywords.LengthUnit;
} else if (base.equals(Units.RADIAN)) {
keyword = isSimplified ? WKTKeywords.Unit : WKTKeywords.AngleUnit;
} else if (base.equals(Units.UNITY)) {
keyword = isSimplified ? WKTKeywords.Unit : WKTKeywords.ScaleUnit;
} else if (base.equals(Units.SECOND)) {
// "Unit" alone is not allowed for time units according ISO 19162.
keyword = WKTKeywords.TimeUnit;
} else {
keyword = WKTKeywords.ParametricUnit;
}
openElement(false, keyword);
setColor(ElementKind.UNIT);
final int fromIndex = buffer.appendCodePoint(symbols.getOpeningQuote(0)).length();
unitFormat.format(unit, buffer, dummy);
closeQuote(fromIndex);
resetColor();
final double conversion = Units.toStandardUnit(unit);
if (Double.isNaN(conversion) && Units.isAngular(unit)) {
// Presume that we have sexagesimal degrees (see below).
appendExact(Math.PI / 180);
} else {
appendExact(conversion);
}
/*
* The EPSG code in UNIT elements is generally not recommended. But we make an exception for sexagesimal
* units (EPSG:9108, 9110 and 9111) because they can not be represented by a simple scale factor in WKT.
* Those units are identified by a conversion factor set to NaN since the conversion is non-linear.
*/
if (convention == Convention.INTERNAL || Double.isNaN(conversion)) {
final Integer code = Units.getEpsgCode(unit, getEnclosingElement(1) instanceof CoordinateSystemAxis);
if (code != null) {
openElement(false, isWKT1 ? WKTKeywords.Authority : WKTKeywords.Id);
append(Constants.EPSG, null);
if (isWKT1) {
append(code.toString(), null);
} else {
append(code);
}
closeElement(false);
}
}
closeElement(false);
/*
* ISO 19162 requires the conversion factor to be positive.
* In addition, keywords other than "Unit" are not valid in WKt 1.
*/
if (!(conversion > 0) || (keyword != WKTKeywords.Unit && isWKT1)) {
setInvalidWKT(Unit.class, null);
}
}
}
use of org.opengis.referencing.cs.CoordinateSystemAxis in project sis by apache.
the class VerticalInfo method complete.
/**
* Completes the extent with a new CRS using the units specified at construction time.
* The CRS created by this method is implementation-dependent. The only guarantees are:
*
* <ul>
* <li>datum type is {@link VerticalDatumType#GEOIDAL},</li>
* <li>increasing height values are up, and</li>
* <li>axis unit of measurement is the given linear unit.</li>
* </ul>
*
* If this method can not propose a suitable CRS, then it returns {@code this}.
*/
final VerticalInfo complete(final CRSFactory crsFactory, final CSFactory csFactory) throws FactoryException {
if (next != null) {
next = next.complete(crsFactory, csFactory);
}
if (compatibleCRS == null) {
return this;
}
final Object name;
final String abbreviation;
CoordinateSystemAxis axis = compatibleCRS.getCoordinateSystem().getAxis(0);
final boolean isUP = AxisDirection.UP.equals(axis.getDirection());
if (isUP) {
name = axis.getName();
abbreviation = axis.getAbbreviation();
} else {
name = AxisNames.GRAVITY_RELATED_HEIGHT;
abbreviation = "H";
}
axis = csFactory.createCoordinateSystemAxis(properties(name), abbreviation, AxisDirection.UP, unit);
/*
* Naming policy (based on usage of names in the EPSG database):
*
* - We can reuse the old axis name if (and only if) the direction is the same, because the axis
* names are constrained by the ISO 19111 specification in a way that do not include the units
* of measurement. Examples: "Gravity-related height", "Depth".
*
* - We can not reuse the previous Coordinate System name, because it often contains the axis
* abbreviation and unit. Examples: "Vertical CS. Axis: height (H). Orientation: up. UoM: m.".
* Since we are lazy, we will reuse the axis name instead, which is more neutral.
*
* - We generally can reuse the CRS name because those names tend to refer to the datum (which is
* unchanged) rather than the coordinate system. Examples: "Low Water depth", "NGF Lallemand height",
* "JGD2011 (vertical) height". However we make an exception if the direction is down, because in such
* cases the previous name may contain terms like "depth", which are not appropriate for our new CRS.
*/
final VerticalCS cs = csFactory.createVerticalCS(properties(axis.getName()), axis);
extent.setVerticalCRS(crsFactory.createVerticalCRS(properties((isUP ? compatibleCRS : axis).getName()), compatibleCRS.getDatum(), cs));
return next;
}
use of org.opengis.referencing.cs.CoordinateSystemAxis in project sis by apache.
the class CRS method getVerticalComponent.
/**
* Returns the first vertical coordinate reference system found in the given CRS, or {@code null} if there is none.
* If the given CRS is already an instance of {@code VerticalCRS}, then this method returns it as-is.
* Otherwise if the given CRS is compound, then this method searches for the first vertical component
* in the order of the {@linkplain #getSingleComponents(CoordinateReferenceSystem) single components list}.
*
* <div class="section">Height in a three-dimensional geographic CRS</div>
* In ISO 19111 model, ellipsoidal heights are indissociable from geographic CRS because such heights
* without their (<var>latitude</var>, <var>longitude</var>) locations make little sense. Consequently
* a standard-conformant library should return {@code null} when asked for the {@code VerticalCRS}
* component of a geographic CRS. This is what {@code getVerticalComponent(…)} does when the
* {@code allowCreateEllipsoidal} argument is {@code false}.
*
* <p>However in some exceptional cases, handling ellipsoidal heights like any other kind of heights
* may simplify the task. For example when computing <em>difference</em> between heights above the
* same datum, the impact of ignoring locations may be smaller (but not necessarily canceled).
* Orphan {@code VerticalCRS} may also be useful for information purpose like labeling a plot axis.
* If the caller feels confident that ellipsoidal heights are safe for his task, he can set the
* {@code allowCreateEllipsoidal} argument to {@code true}. In such case, this {@code getVerticalComponent(…)}
* method will create a temporary {@code VerticalCRS} from the first three-dimensional {@code GeographicCRS}
* <em>in last resort</em>, only if it can not find an existing {@code VerticalCRS} instance.
* <strong>Note that this is not a valid CRS according ISO 19111</strong> — use with care.</p>
*
* @param crs the coordinate reference system, or {@code null}.
* @param allowCreateEllipsoidal {@code true} for allowing the creation of orphan CRS for ellipsoidal heights.
* The recommended value is {@code false}.
* @return the first vertical CRS, or {@code null} if none.
*
* @see #compound(CoordinateReferenceSystem...)
*
* @category information
*/
public static VerticalCRS getVerticalComponent(final CoordinateReferenceSystem crs, final boolean allowCreateEllipsoidal) {
if (crs instanceof VerticalCRS) {
return (VerticalCRS) crs;
}
if (crs instanceof CompoundCRS) {
final CompoundCRS cp = (CompoundCRS) crs;
boolean a = false;
do {
// Executed at most twice.
for (final CoordinateReferenceSystem c : cp.getComponents()) {
final VerticalCRS candidate = getVerticalComponent(c, a);
if (candidate != null) {
return candidate;
}
}
} while ((a = !a) == allowCreateEllipsoidal);
}
if (allowCreateEllipsoidal && horizontalCode(crs) == 3) {
final CoordinateSystem cs = crs.getCoordinateSystem();
final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
if (i >= 0) {
final CoordinateSystemAxis axis = cs.getAxis(i);
VerticalCRS c = CommonCRS.Vertical.ELLIPSOIDAL.crs();
if (!c.getCoordinateSystem().getAxis(0).equals(axis)) {
final Map<String, ?> properties = IdentifiedObjects.getProperties(c);
c = new DefaultVerticalCRS(properties, c.getDatum(), new DefaultVerticalCS(properties, axis));
}
return c;
}
}
return null;
}
use of org.opengis.referencing.cs.CoordinateSystemAxis in project sis by apache.
the class ReferencingFunctions method getAxis.
/**
* Returns the axis name and units for the specified dimension in a coordinate reference system or coordinate system.
* This method returns a short axis name as used in Well Known Text (WKT) format, for example <cite>"Latitude"</cite>
* instead of <cite>"Geodetic latitude"</cite>.
*
* @param codeOrPath the code allocated by an authority, or the path to a file.
* @param dimension the dimension (1, 2, …).
* @return the name of the requested axis.
*/
@Override
public String getAxis(final String codeOrPath, final int dimension) {
final CacheKey<String> key = new CacheKey<>(String.class, codeOrPath, dimension, null);
String name = key.peek();
if (name == null) {
final Cache.Handler<String> handler = key.lock();
try {
name = handler.peek();
if (name == null) {
final IdentifiedObject object;
try {
object = getIdentifiedObject(codeOrPath, null);
} catch (Exception exception) {
return getLocalizedMessage(exception);
}
CoordinateSystem cs = null;
final CoordinateSystemAxis axis;
if (object instanceof CoordinateSystemAxis) {
axis = (CoordinateSystemAxis) object;
} else {
if (object instanceof CoordinateReferenceSystem) {
cs = ((CoordinateReferenceSystem) object).getCoordinateSystem();
} else if (object instanceof CoordinateSystem) {
cs = (CoordinateSystem) object;
} else {
final Class<?> actual;
if (object instanceof AbstractIdentifiedObject) {
actual = ((AbstractIdentifiedObject) object).getInterface();
} else {
actual = Classes.getClass(object);
}
return Errors.getResources(getJavaLocale()).getString(Errors.Keys.UnexpectedTypeForReference_3, codeOrPath, CoordinateReferenceSystem.class, actual);
}
if (dimension >= 1 && dimension <= cs.getDimension()) {
axis = cs.getAxis(dimension - 1);
} else {
return Errors.getResources(getJavaLocale()).getString(Errors.Keys.IndexOutOfBounds_1, dimension);
}
}
final String unit = axis.getUnit().toString();
name = Transliterator.DEFAULT.toShortAxisName(cs, axis.getDirection(), axis.getName().getCode());
if (unit != null && !unit.isEmpty()) {
name = name + " (" + unit + ')';
}
}
} finally {
handler.putAndUnlock(name);
}
}
return name;
}
Aggregations