use of org.opengis.referencing.cs.AxisDirection in project sis by apache.
the class CoordinateSystems method swapAndScaleAxes.
/**
* Returns an affine transform between two coordinate systems.
* Only units and axes order (e.g. transforming from
* ({@linkplain AxisDirection#NORTH North}, {@linkplain AxisDirection#WEST West}) to
* ({@linkplain AxisDirection#EAST East}, {@linkplain AxisDirection#NORTH North})
* are taken in account by this method.
*
* <div class="section">Conditions</div>
* The two coordinate systems must implement the same GeoAPI coordinate system interface.
* For example if {@code sourceCS} is a {@link org.opengis.referencing.cs.CartesianCS},
* then {@code targetCS} must be a {@code CartesianCS} too.
*
* <div class="note"><b>Example:</b>
* If coordinates in {@code sourceCS} are (<var>x</var>,<var>y</var>) tuples in metres
* and coordinates in {@code targetCS} are (<var>-y</var>,<var>x</var>) tuples in centimetres,
* then the transformation can be performed as below:
*
* {@preformat math
* ┌ ┐ ┌ ┐ ┌ ┐
* │-y(cm)│ │ 0 -100 0 │ │ x(m)│
* │ x(cm)│ = │ 100 0 0 │ │ y(m)│
* │ 1 │ │ 0 0 1 │ │ 1 │
* └ ┘ └ ┘ └ ┘
* }
* </div>
*
* @param sourceCS the source coordinate system.
* @param targetCS the target coordinate system.
* @return the conversion from {@code sourceCS} to {@code targetCS} as an affine transform.
* Only axis direction and units are taken in account.
* @throws IllegalArgumentException if the CS are not of the same type, or axes do not match.
* @throws IncommensurableException if the units are not compatible, or the conversion is non-linear.
*
* @see Matrices#createTransform(AxisDirection[], AxisDirection[])
*/
@SuppressWarnings("fallthrough")
public static Matrix swapAndScaleAxes(final CoordinateSystem sourceCS, final CoordinateSystem targetCS) throws IllegalArgumentException, IncommensurableException {
ensureNonNull("sourceCS", sourceCS);
ensureNonNull("targetCS", targetCS);
if (!Classes.implementSameInterfaces(sourceCS.getClass(), targetCS.getClass(), CoordinateSystem.class)) {
throw new IllegalArgumentException(Resources.format(Resources.Keys.IncompatibleCoordinateSystemTypes));
}
final AxisDirection[] srcAxes = getAxisDirections(sourceCS);
final AxisDirection[] dstAxes = getAxisDirections(targetCS);
final MatrixSIS matrix = Matrices.createTransform(srcAxes, dstAxes);
assert Arrays.equals(srcAxes, dstAxes) == matrix.isIdentity() : matrix;
/*
* The previous code computed a matrix for swapping axes. Usually, this
* matrix contains only 0 and 1 values with only one "1" value by row.
* For example, the matrix operation for swapping x and y axes is:
* ┌ ┐ ┌ ┐ ┌ ┐
* │y│ │ 0 1 0 │ │x│
* │x│ = │ 1 0 0 │ │y│
* │1│ │ 0 0 1 │ │1│
* └ ┘ └ ┘ └ ┘
* Now, take in account units conversions. Each matrix's element (j,i)
* is multiplied by the conversion factor from sourceCS.getUnit(i) to
* targetCS.getUnit(j). This is an element-by-element multiplication,
* not a matrix multiplication. The last column is processed in a special
* way, since it contains the offset values.
*/
// == sourceCS.getDimension()
final int sourceDim = matrix.getNumCol() - 1;
// == targetCS.getDimension()
final int targetDim = matrix.getNumRow() - 1;
for (int j = 0; j < targetDim; j++) {
final Unit<?> targetUnit = targetCS.getAxis(j).getUnit();
for (int i = 0; i < sourceDim; i++) {
if (matrix.getElement(j, i) == 0) {
// (i.e. axes are orthogonal).
continue;
}
final Unit<?> sourceUnit = sourceCS.getAxis(i).getUnit();
if (Objects.equals(sourceUnit, targetUnit)) {
// between source[i] and target[j].
continue;
}
Number scale = 1;
Number offset = 0;
final Number[] coefficients = Units.coefficients(sourceUnit.getConverterToAny(targetUnit));
switch(coefficients != null ? coefficients.length : -1) {
// Fall through
case 2:
scale = coefficients[1];
// Fall through
case 1:
offset = coefficients[0];
case 0:
break;
default:
throw new IncommensurableException(Resources.format(Resources.Keys.NonLinearUnitConversion_2, sourceUnit, targetUnit));
}
final DoubleDouble element = DoubleDouble.castOrCopy(matrix.getNumber(j, i));
final DoubleDouble r = new DoubleDouble(element);
r.multiply(scale);
matrix.setNumber(j, i, r);
r.setFrom(element);
r.multiply(offset);
r.add(matrix.getNumber(j, sourceDim));
matrix.setNumber(j, sourceDim, r);
}
}
return matrix;
}
use of org.opengis.referencing.cs.AxisDirection in project sis by apache.
the class Normalizer method compareTo.
/**
* Compares two axis for an order that try to favor right-handed coordinate systems.
* Compass directions like North and East are first. Vertical directions like Up or Down are next.
*/
@Override
public int compareTo(final Normalizer that) {
int d = unitOrder - that.unitOrder;
if (d == 0) {
final AxisDirection d1 = this.axis.getDirection();
final AxisDirection d2 = that.axis.getDirection();
d = AxisDirections.angleForCompass(d2, d1);
if (d == Integer.MIN_VALUE) {
if (meridian != null) {
if (that.meridian != null) {
d = meridian.compareTo(that.meridian);
} else {
d = -1;
}
} else if (that.meridian != null) {
d = +1;
} else {
d = order(d1) - order(d2);
}
}
}
return d;
}
use of org.opengis.referencing.cs.AxisDirection in project sis by apache.
the class DefaultCoordinateSystemAxis method formatTo.
/**
* Formats this axis as a <cite>Well Known Text</cite> {@code Axis[…]} element.
*
* <div class="section">Constraints for WKT validity</div>
* The ISO 19162 specification puts many constraints on axis names, abbreviations and directions allowed in WKT.
* Most of those constraints are inherited from ISO 19111 — see {@link CoordinateSystemAxis} javadoc for some of
* those. The current Apache SIS implementation does not verify whether this axis name and abbreviation are
* compliant; we assume that the user created a valid axis.
* The only actions (derived from ISO 19162 rules) taken by this method (by default) are:
*
* <ul>
* <li>Replace <cite>“Geodetic latitude”</cite> and <cite>“Geodetic longitude”</cite> names (case insensitive)
* by <cite>“latitude”</cite> and <cite>“longitude”</cite> respectively.</li>
* <li>For latitude and longitude axes, replace “φ” and “λ” abbreviations by <var>“B”</var> and <var>“L”</var>
* respectively (from German “Breite” and “Länge”, used in academic texts worldwide).
* Note that <var>“L”</var> is also the transliteration of Greek letter “lambda” (λ).</li>
* <li>In {@link SphericalCS}, replace “φ” and “θ” abbreviations by <var>“U”</var> and <var>“V”</var> respectively.</li>
* <li>In {@link PolarCS}, replace “θ” abbreviation by <var>“U”</var>.</li>
* </ul>
*
* The above-cited replacements of name and Greek letters can be controlled by a call to
* {@link org.apache.sis.io.wkt.WKTFormat#setTransliterator(Transliterator)}.
*
* @return {@code "Axis"}.
*
* @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#39">WKT 2 specification §7.5.3</a>
*/
@Override
protected String formatTo(final Formatter formatter) {
final Convention convention = formatter.getConvention();
final boolean isWKT1 = (convention.majorVersion() == 1);
final boolean isInternal = (convention == Convention.INTERNAL);
final CoordinateSystem cs = getEnclosingCS(formatter);
AxisDirection dir = getDirection();
String name = IdentifiedObjects.getName(this, formatter.getNameAuthority());
if (name == null) {
name = IdentifiedObjects.getName(this, null);
}
if (name != null && !isInternal) {
final String old = name;
name = formatter.getTransliterator().toShortAxisName(cs, dir, name);
if (name == null && isWKT1) {
// WKT 1 does not allow omission of name.
name = old;
}
}
/*
* ISO 19162:2015 §7.5.3 suggests to put abbreviation in parentheses, e.g. "Easting (x)".
* The specification also suggests to write only the abbreviation (e.g. "(X)") in the
* special case of Geocentric axis, and disallows Greek letters.
*/
if (!isWKT1) {
final String a = formatter.getTransliterator().toLatinAbbreviation(cs, dir, getAbbreviation());
if (a != null && !a.equals(name)) {
final StringBuilder buffer = new StringBuilder();
if (name != null) {
buffer.append(name).append(' ');
}
name = buffer.append('(').append(a).append(')').toString();
}
}
formatter.append(name, ElementKind.AXIS);
/*
* Format the axis direction, optionally followed by a MERIDIAN[…] element
* if the direction is of the kind "South along 90°N" for instance.
*/
DirectionAlongMeridian meridian = null;
if (AxisDirections.isUserDefined(dir)) {
meridian = DirectionAlongMeridian.parse(dir);
if (meridian != null) {
dir = meridian.baseDirection;
if (isWKT1) {
formatter.setInvalidWKT(this, null);
}
}
}
formatter.append(dir);
formatter.append(meridian);
/*
* Formats the axis unit only if the enclosing CRS element does not provide one.
* If the enclosing CRS provided a contextual unit, then it is assumed to apply
* to all axes (we do not verify).
*/
if (!isWKT1) {
if (convention == Convention.WKT2 && cs != null) {
final Order order = Order.create(cs, this);
if (order != null) {
formatter.append(order);
} else {
formatter.setInvalidWKT(cs, null);
}
}
if (!formatter.hasContextualUnit(1)) {
formatter.append(getUnit());
}
}
return WKTKeywords.Axis;
}
use of org.opengis.referencing.cs.AxisDirection in project sis by apache.
the class AxisDirections method valueOf.
/**
* Searches pre-defined {@link AxisDirection} for a given name. This method searches for a match in the set
* of known axis directions as returned by {@link AxisDirection#values()}, plus a few special cases like
* <cite>"Geocentre > equator/90°E"</cite>. The later are used in the EPSG database for geocentric CRS.
*
* <p>This method does not know about {@code org.apache.sis.referencing.cs.DirectionAlongMeridian}.
* The later is a parser which may create new directions, while this method searches only in a set
* of predefined directions and never create new ones.</p>
*
* @param name the name of the axis direction to search.
* @return the first axis direction having a name matching the given one, or {@code null} if none.
*/
public static AxisDirection valueOf(String name) {
name = trimWhitespaces(name.replace('_', ' '));
final AxisDirection[] directions = AxisDirection.values();
AxisDirection candidate = find(name, directions);
if (candidate == null) {
/*
* No match found when using the pre-defined axis name. Searches among
* the set of geocentric directions. Expected directions are:
*
* Geocentre > equator/PM or Geocentre > equator/0°E
* Geocentre > equator/90dE or Geocentre > equator/90°E
* Geocentre > north pole
*/
int d = name.indexOf('>');
if (d >= 0 && equalsIgnoreCase(name, 0, skipTrailingWhitespaces(name, 0, d), "Geocentre")) {
final int length = name.length();
d = skipLeadingWhitespaces(name, d + 1, length);
int s = name.indexOf('/', d);
if (s < 0) {
if (equalsIgnoreCase(name, d, length, "north pole")) {
// "Geocentre > north pole"
return GEOCENTRIC_Z;
}
} else if (equalsIgnoreCase(name, d, skipTrailingWhitespaces(name, d, s), "equator")) {
s = skipLeadingWhitespaces(name, s + 1, length);
if (equalsIgnoreCase(name, s, length, "PM")) {
// "Geocentre > equator/PM"
return GEOCENTRIC_X;
}
/*
* At this point, the name may be "Geocentre > equator/0°E",
* "Geocentre > equator/90°E" or "Geocentre > equator/90dE".
* Parse the number, limiting the scan to 6 characters for
* avoiding a NumberFormatException.
*/
final int stopAt = Math.min(s + 6, length);
for (int i = s; i < stopAt; i++) {
final char c = name.charAt(i);
if (c < '0' || c > '9') {
if (i == s)
break;
final int n = Integer.parseInt(name.substring(s, i));
i = skipLeadingWhitespaces(name, i, length);
if (equalsIgnoreCase(name, i, length, "°E") || equalsIgnoreCase(name, i, length, "dE")) {
switch(n) {
// "Geocentre > equator/0°E"
case 0:
return GEOCENTRIC_X;
// "Geocentre > equator/90°E"
case 90:
return GEOCENTRIC_Y;
}
}
break;
}
}
}
}
}
return candidate;
}
use of org.opengis.referencing.cs.AxisDirection in project sis by apache.
the class AxisDirectionsTest method testAngleForCompass.
/**
* Tests the {@link AxisDirections#angleForCompass(AxisDirection, AxisDirection)} method.
*/
@Test
public void testAngleForCompass() {
final AxisDirection[] compass = new AxisDirection[] { NORTH, NORTH_NORTH_EAST, NORTH_EAST, EAST_NORTH_EAST, EAST, EAST_SOUTH_EAST, SOUTH_EAST, SOUTH_SOUTH_EAST, SOUTH, SOUTH_SOUTH_WEST, SOUTH_WEST, WEST_SOUTH_WEST, WEST, WEST_NORTH_WEST, NORTH_WEST, NORTH_NORTH_WEST };
assertEquals(compass.length, AxisDirections.COMPASS_COUNT);
final int base = NORTH.ordinal();
final int h = compass.length / 2;
for (int i = 0; i < compass.length; i++) {
final AxisDirection direction = compass[i];
final AxisDirection opposite = AxisDirections.opposite(direction);
final String message = direction.name();
int io = i + h, in = i;
if (i >= h)
io -= AxisDirections.COMPASS_COUNT;
if (i > h)
in -= AxisDirections.COMPASS_COUNT;
assertEquals(message, base + i, direction.ordinal());
assertEquals(message, base + io, opposite.ordinal());
assertEquals(message, 0, AxisDirections.angleForCompass(direction, direction));
assertEquals(message, h, abs(AxisDirections.angleForCompass(direction, opposite)));
assertEquals(message, in, AxisDirections.angleForCompass(direction, NORTH));
}
}
Aggregations