blob: e04b8d095423fae65fd178114fd42e19eedc8866 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.coverage.grid;
import java.util.Objects;
import java.io.Serializable;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.datum.PixelInCell;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
/**
* High-level description about how a grid is orientated relative to the CRS axes. The orientation of a grid
* is closely related to the {@linkplain GridGeometry#getGridToCRS(PixelInCell) grid to CRS} transform.
* For example, the conversion from grid coordinates to CRS coordinates may flip the <var>y</var> axis
* (grid coordinates increasing toward down on screen), or may swap <var>x</var> and <var>y</var> axes, <i>etc.</i>
* The constants enumerated in this class cover only a few common cases where the grid is
* <a href="https://en.wikipedia.org/wiki/Axis-aligned_object">axis-aligned</a> with the CRS.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
*
* @see GridGeometry#GridGeometry(GridExtent, Envelope, GridOrientation)
*
* @since 1.1
*/
public final class GridOrientation implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -1354776950822418237L;
/**
* The {@code gridToCRS} transform applies only scales and translations (no axis flip or swap).
* Moving along the grid axis in dimension <var>i</var> causes a displacement along the CRS axis
* in the same dimension <var>i</var>.
* In matrix terms all non-zero coefficients are on the diagonal or in the translation column.
* For example, in the three-dimensional case:
*
* <pre class="math">
* ┌ ┐
* │ Sx 0 0 Tx │
* │ 0 Sy 0 Ty │
* │ 0 0 Sz Tz │
* │ 0 0 0 1 │
* └ ┘</pre>
*
* with
* <var>S<sub>x</sub></var> &gt; 0,
* <var>S<sub>y</sub></var> &gt; 0 and
* <var>S<sub>z</sub></var> &gt; 0.
*/
public static final GridOrientation HOMOTHETY = new GridOrientation(0, null, false);
/**
* The {@code gridToCRS} transform applies scales and translations with a flip of the second axis (<var>y</var>).
* This is equivalent to {@code HOMOTHETY.flipGridAxis(1)}; i.e.
* this is the same kind of conversion than {@link #HOMOTHETY} except that the <var>S<sub>y</sub></var> term in
* the matrix is replaced by −<var>S<sub>y</sub></var> and the <var>T<sub>y</sub></var> term has a different value.
* For example in the three-dimensional case, the {@code gridToCRS} transform is:
*
* <pre class="math">
* ┌ ┐
* │ Sx 0 0 Tx │
* │ 0 −Sy 0 Ty′ │
* │ 0 0 Sz Tz │
* │ 0 0 0 1 │
* └ ┘</pre>
*
* <h4>When to use</h4>
* {@code REFLECTION_Y} is commonly used when the grid is a {@link java.awt.image.RenderedImage}.
* By contrast, an {@link #HOMOTHETY} transform often results in <var>y</var> axis oriented toward up,
* instead of down as commonly expected with rendered images.
* This {@code REFLECTION_Y} value matches the common usage for grids backed by images.
*
* @see #flipGridAxis(int)
*/
public static final GridOrientation REFLECTION_Y = new GridOrientation(2, null, false);
/**
* CRS axes are reordered and oriented toward directions commonly used for displaying purpose.
* This is equivalent to {@code REFLECTION_Y.useVariantOfCRS(AxesConvention.DISPLAY_ORIENTED)}.
* {@link GridGeometry}s created with this orientation have properties computed as below:
*
* <ul>
* <li>The {@link GridExtent} specified by user (never modified).</li>
* <li>An envelope initialized to user-specified envelope (potentially modified as described below).</li>
* <li>A {@code gridToCRS} initialized to {@link #REFLECTION_Y} (potentially modified as described below).</li>
* <li>The {@linkplain AxesConvention#DISPLAY_ORIENTED display oriented} variant of the CRS specified by user.</li>
* <li>If above CRS variant is same as user-specified CRS, we are done. Otherwise:
* <ul>
* <li>Envelope dimensions are reordered to match axis order in above CRS variant
* Those changes are applied on a copy of user-specified envelope.</li>
* <li>The {@code gridToCRS} transform is amended with the same reordering (applied on columns)
* as for envelope.</li>
* </ul>
* </li>
* </ul>
*
* Below is an example of {@code gridToCRS} transform obtained when the display-oriented CRS variant
* is different than the user-specified CRS (if those CRSs are equal, then the transform is rather
* like the one shown in {@link #REFLECTION_Y}):
*
* <pre class="math">
* ┌ ┐
* │ 0 Sx 0 Tx │
* │ −Sy 0 0 Ty′ │
* │ 0 0 Sz Tz │
* │ 0 0 0 1 │
* └ ┘</pre>
*
* <h4>When to use</h4>
* This orientation can be used for deriving a coordinate reference system with the
* (<var>longitude</var>, <var>latitude</var>) or (<var>x</var>,<var>y</var>) axis order,
* but without altering grid axes order.
*
* <h4>Alternative</h4>
* {@code DISPLAY.canReorderGridAxis(true)} is an alternative where grid axes get the same reordering as CRS axes.
* Consequently, the {@link GridExtent} may be different then the specified extent
* but the {@code gridToCRS} transform always has the form shown in {@link #REFLECTION_Y}.
* This alternative can be used for deriving a coordinate reference system with the
* (<var>longitude</var>, <var>latitude</var>) or (<var>x</var>,<var>y</var>) axis order,
* and modify grid cell layout (i.e. replace the {@link GridExtent} instance)
* in way that allows {@link java.awt.image.RenderedImage} to appear with expected orientation.
*
* @see #useVariantOfCRS(AxesConvention)
* @see AxesConvention#DISPLAY_ORIENTED
*/
public static final GridOrientation DISPLAY = new GridOrientation(2, AxesConvention.DISPLAY_ORIENTED, false);
/**
* Set of grid axes to reverse, as a bit mask. For any dimension <var>i</var>, the bit
* at {@code 1L << i} is set to 1 if the grid axis at that dimension should be flipped.
* This is the argument to give in calls to {@link GridExtent#cornerToCRS(Envelope, long, int[])}.
*
* @see #flipGridAxis(int)
*/
final long flippedAxes;
/**
* If the user-specified CRS should be substituted by a variant of that CRS, the variant to use.
* Otherwise {@code null}. If non-null, either the {@code gridToCRS} matrix may be non-diagonal
* or the {@link GridExtent} axes may be ordered, depending on {@link #canReorderGridAxis} value.
*
* @see #useVariantOfCRS(AxesConvention)
*/
final AxesConvention crsVariant;
/**
* Whether {@link GridExtent} can be rewritten with a different axis order
* for matching the CRS axis order specified by {@link #crsVariant}.
* If {@code false}, then axis order changes will be handled in the {@code gridToCRS} transform instead.
*
* @see #canReorderGridAxis(boolean)
*/
final boolean canReorderGridAxis;
/**
* Creates a new enumeration value.
*/
private GridOrientation(final long flippedAxes, final AxesConvention crsVariant, final boolean canReorderGridAxis) {
this.flippedAxes = flippedAxes;
this.crsVariant = crsVariant;
this.canReorderGridAxis = canReorderGridAxis;
}
/**
* Reverses axis direction in the specified grid dimension.
* For example if grid indices are (<var>column</var>, <var>row</var>),
* then {@code flipGridAxis(1)} will reverse the direction of rows axis.
* Invoking this method a second time for the same dimension will cancel the flipping.
*
* @param dimension index of the dimension in the grid on which to apply direction reversal.
* @return a grid orientation equals to this one except for the axis flip in specified dimension.
*
* @see #REFLECTION_Y
* @see GridCoverageBuilder#flipGridAxis(int)
*/
public GridOrientation flipGridAxis(final int dimension) {
ArgumentChecks.ensurePositive("dimension", dimension);
if (dimension >= Long.SIZE) {
throw new ArithmeticException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, dimension + 1));
}
return new GridOrientation(flippedAxes ^ (1L << dimension), crsVariant, canReorderGridAxis);
}
/**
* Substitutes the user-specified CRS by a variant of it, for example with different axis order.
* If the CRS axis order changed as a result of this substitution, then:
*
* <ul>
* <li>Order of envelope coordinates are changed accordingly.</li>
* <li>CRS axis order change is mapped to the grid in one of the following ways:
* <ul>
* <li>If {@code canReorderGridAxis(true)} has been invoked, then the same change is applied
* on grid axis order. Consequently, grid axes and CRS axes stay in the same order,
* but the resulting {@link GridExtent} may be different than the specified one.</li>
* <li>Otherwise {@link GridExtent} stay unchanged and axis order change is handled in the
* {@code gridToCRS} transform instead.</li>
* </ul>
* </li>
* </ul>
*
* <h4>Limitations</h4>
* Current implementation accepts only axis order changes and direction changes.
* The units of measurement changes are not yet supported.
* Consequently, {@link AxesConvention#NORMALIZED} is not accepted.
*
* @param variant the kind of substitution to apply on CRS, or {@code null} if none.
* @return a grid orientation equals to this one except that it uses the specified CRS variant.
*
* @see #DISPLAY
*/
public GridOrientation useVariantOfCRS(final AxesConvention variant) {
if (variant == crsVariant) {
return this;
}
if (variant == AxesConvention.NORMALIZED || variant == AxesConvention.ORIGINAL) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedArgumentValue_1, variant));
}
return new GridOrientation(flippedAxes, variant, canReorderGridAxis);
}
/**
* Specifies whether a change of CRS axis order should be accompanied by an equivalent change of grid axis order.
* A value of {@code true} implies that user-specified {@link GridExtent} may be replaced by a different extent.
* If {@code false} (the default), then axis order changes will be handled in the {@code gridToCRS} transform
* instead.
*
* @param enabled whether changes of CRS axis order should be reflected by changes of grid axis order.
* @return a grid orientation equals to this one except that it has the specified flag.
*/
public GridOrientation canReorderGridAxis(final boolean enabled) {
if (enabled == canReorderGridAxis) {
return this;
}
return new GridOrientation(flippedAxes, crsVariant, enabled);
}
/**
* Returns whether this object is equal to the given object.
*
* @param other the other object to compare with this object.
* @return whether the two objects are equal.
*/
@Override
public boolean equals(final Object other) {
if (other instanceof GridOrientation) {
final GridOrientation that = (GridOrientation) other;
return flippedAxes == that.flippedAxes &&
crsVariant == that.crsVariant &&
canReorderGridAxis == that.canReorderGridAxis;
}
return false;
}
/**
* Returns a hash code value for this grid orientation.
*/
@Override
public int hashCode() {
return Long.hashCode(flippedAxes) + Objects.hashCode(crsVariant) + Boolean.hashCode(canReorderGridAxis);
}
/**
* Returns a string representation of this grid orientation.
* This is for debugging purpose and may change in any future version.
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(getClass().getSimpleName()).append('[');
String separator = "";
if (flippedAxes != 0) {
buffer.append("flip={");
long f = flippedAxes;
do {
final long i = Long.numberOfTrailingZeros(f);
buffer.append(separator).append(i);
f &= ~(1L << i);
separator = ", ";
} while (f != 0);
buffer.append('}');
}
if (crsVariant != null) {
buffer.append(separator).append("crs=").append(crsVariant);
if (canReorderGridAxis) {
buffer.append(" with grid axis reordering");
}
}
return buffer.append(']').toString();
}
}