This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 93d99ceb8285c94da2c56ea24564934d55a3abe7 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed May 27 17:25:10 2020 +0200 Javadoc editions. Move method close to related methods. Share some code. Take in account the variable number of dimensions (2 or pseudo-3) in JTS geometries. --- .../org/apache/sis/coverage/grid/GridExtent.java | 94 +++++++++++++++------- .../sis/internal/feature/GeometryWithCRS.java | 18 ++++- .../sis/internal/feature/GeometryWrapper.java | 4 +- .../apache/sis/internal/feature/esri/Wrapper.java | 9 +-- .../sis/internal/feature/j2d/PointWrapper.java | 10 ++- .../apache/sis/internal/feature/j2d/Wrapper.java | 9 +-- .../apache/sis/internal/feature/jts/Wrapper.java | 41 +++++++--- .../apache/sis/coverage/grid/GridExtentTest.java | 66 +++++++++------ 8 files changed, 166 insertions(+), 85 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java index b1f1639..5b258f6 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java @@ -21,13 +21,13 @@ import java.util.HashMap; import java.util.Arrays; import java.util.Optional; import java.util.Locale; -import java.util.stream.LongStream; import java.io.Serializable; import java.io.IOException; import java.io.UncheckedIOException; import org.opengis.util.FactoryException; import org.opengis.geometry.Envelope; import org.opengis.geometry.DirectPosition; +import org.opengis.geometry.MismatchedDimensionException; import org.opengis.metadata.spatial.DimensionNameType; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CoordinateSystem; @@ -83,6 +83,7 @@ import org.opengis.coverage.PointOutsideCoverageException; * The same instance can be shared by different {@link GridGeometry} instances.</p> * * @author Martin Desruisseaux (IRD, Geomatys) + * @author Alexis Manin (Geomatys) * @version 1.1 * @since 1.0 * @module @@ -503,7 +504,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable * @param enclosing the extent from which to copy axes, or {@code null} if none. * @param coordinates the coordinates. This array is not cloned. */ - GridExtent(final GridExtent enclosing, final long... coordinates) { + GridExtent(final GridExtent enclosing, final long[] coordinates) { this.coordinates = coordinates; types = (enclosing != null) ? enclosing.types : null; assert (types == null) || types.length == getDimension(); @@ -593,10 +594,23 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable * This is a very common case since many grids start their cell numbering at zero. * * @return whether all low coordinates are zero. + * + * @see #translate(long...) */ public boolean startsAtZero() { - for (int i = getDimension(); --i >= 0;) { - if (coordinates[i] != 0) { + return isZero(coordinates, getDimension()); + } + + /** + * Returns {@code true} if all values in the given vector are zero. + * + * @param vector the vector to verify. + * @param n number of elements to verify. All remaining elements are ignored. + * @return whether the <var>n</var> first elements in the given array are all zero. + */ + private static boolean isZero(final long[] vector, int n) { + while (--n >= 0) { + if (vector[n] != 0) { return false; } } @@ -1292,6 +1306,51 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable } /** + * Returns an extent translated by the given amount of cells compared to this extent. + * The returned extent has the same {@linkplain #getSize(int) size} than this extent, + * i.e. both low and high grid coordinates are displaced by the same amount of cells. + * The given translation vector should have a length equal to this extent + * {@linkplain #getDimension() dimension}, but shorter arrays are allowed: + * missing values are assumed zero (i.e. the extent is not translated in missing dimensions). + * + * <div class="note"><b>Example:</b> + * for an extent (x: [0…10], y: [2…4], z: [0…1]) and a translation {-2, 2}, + * the resulting extent would be (x: [-2…8], y: [4…6], z: [0…1]).</div> + * + * @param translation translation to apply on each axis in order, or {@code null} if none. + * @return a grid-extent whose coordinates (both low and high ones) have been translated by given amounts. + * If the given translation is a no-op (no value or only 0 ones), then this extent is returned as is. + * @throws MismatchedDimensionException if given translation vector is longer than + * {@linkplain #getDimension() this extent dimension}. + * @throws ArithmeticException if the translation results in coordinates that overflow 64-bits integer. + * + * @see #startsAtZero() + * + * @since 1.1 + */ + public GridExtent translate(final long... translation) { + if (translation != null) { + final int dimension = getDimension(); + if (translation.length > dimension) { + throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, + "translation", translation.length, dimension)); + } + if (!isZero(translation, translation.length)) { + final GridExtent translated = new GridExtent(this); + final long[] c = translated.coordinates; + for (int i=0; i < translation.length; i++) { + final int j = i + dimension; + final long t = translation[i]; + c[i] = Math.addExact(c[i], t); + c[j] = Math.addExact(c[j], t); + } + return translated; + } + } + return this; + } + + /** * Returns a hash value for this grid envelope. This value needs not to remain * consistent between different implementations of the same class. * @@ -1387,31 +1446,4 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable } table.flush(); } - - /** - * Create a fresh translated extent from the current one. A translated extent <em>matches this extent size</em>, - * meaning that both its low and high coordinates are displaced by the same amount. - * - * @param translation Translation to apply to each axis, respectively. If argument dimension is lower than this - * extent, we consider all dimensions non affected as not translated. Meaning that for an extent - * (x: [0..10], y: [2..4], z: [0..1]) and a translation [-2, 2], the resulting extent would be - * (x: [-2..8], y: [4..6], z: [0..1]). - * @return A new/independant grid-extent whose coordinates (both low and high ones) have been translated by a given - * amount. However, if given translation is a no-op (no value or only 0 ones), this extent is returned as is. - * @throws IllegalArgumentException If given translation dimension is greater than {@link #getDimension() this extent dimension}. - */ - public GridExtent translate(long... translation) { - if (translation == null || translation.length < 1) return this; - final int dimension = getDimension(); - if (translation.length > dimension) throw new IllegalArgumentException("Given translation dimension is higher than this extent"); - // In case of badly sized input, this could become a very costly check, so we check dimension before. - if (LongStream.of(translation).allMatch(v -> v == 0)) return this; - final long[] translatedCoords = Arrays.copyOf(coordinates, coordinates.length); - for (int min = 0, max = dimension ; min < translation.length ; min++, max++) { - translatedCoords[min] += translation[min]; - translatedCoords[max] += translation[min]; - } - - return new GridExtent(this, translatedCoords); - } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWithCRS.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWithCRS.java index fabd9dd..27dc246 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWithCRS.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWithCRS.java @@ -16,8 +16,9 @@ */ package org.apache.sis.internal.feature; -import org.apache.sis.util.ArgumentChecks; import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.util.ArgumentChecks; /** @@ -55,13 +56,24 @@ public abstract class GeometryWithCRS<G> extends GeometryWrapper<G> { } /** - * Sets the coordinate reference system, which is assumed two-dimensional (this method does not verify). + * Sets the coordinate reference system, which shall be two-dimensional. * * @param crs the coordinate reference system to set. */ @Override public final void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { - ArgumentChecks.ensureDimensionMatches("crs", 2, crs); + ArgumentChecks.ensureDimensionMatches("crs", Geometries.BIDIMENSIONAL, crs); this.crs = crs; } + + /** + * Creates an initially empty envelope with the CRS of this geometry. + * If this geometry has no CRS, then a two-dimensional envelope is created. + * This is a convenience method for {@link #getEnvelope()} implementations. + * + * @return an initially empty envelope. + */ + protected final GeneralEnvelope createEnvelope() { + return (crs != null) ? new GeneralEnvelope(crs) : new GeneralEnvelope(Geometries.BIDIMENSIONAL); + } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java index d8f200f..8c0ac25 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java @@ -113,8 +113,8 @@ public abstract class GeometryWrapper<G> implements Geometry { /** * Returns the geometry bounding box, together with its coordinate reference system. * - * @return the geometry envelope. Should never be {@code null}. Note though that for an empty geometry or a single - * point, the returned envelope will be empty. + * @return the geometry envelope. Should never be {@code null}. + * Note though that for an empty geometry or a single point, the returned envelope will be empty. */ @Override public abstract GeneralEnvelope getEnvelope(); diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java index 04d586c..996d4a1 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/esri/Wrapper.java @@ -33,7 +33,6 @@ import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.internal.feature.Geometries; import org.apache.sis.internal.feature.GeometryWithCRS; import org.apache.sis.util.Debug; -import org.opengis.referencing.crs.CoordinateReferenceSystem; /** @@ -76,17 +75,15 @@ final class Wrapper extends GeometryWithCRS<Geometry> { } /** - * If the ESRI geometry is non-empty, returns its envelope as an Apache SIS implementation. - * Otherwise returns {@code null}. + * Returns the ESRI envelope as an Apache SIS implementation. * - * @return the envelope, or {@code null} if empty. + * @return the envelope. */ @Override public GeneralEnvelope getEnvelope() { final Envelope2D bounds = new Envelope2D(); geometry.queryEnvelope2D(bounds); - final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); - final GeneralEnvelope env = crs == null ? new GeneralEnvelope(org.apache.sis.internal.feature.j2d.Factory.BIDIMENSIONAL) : new GeneralEnvelope(crs); + final GeneralEnvelope env = createEnvelope(); env.setRange(0, bounds.xmin, bounds.xmax); env.setRange(1, bounds.ymin, bounds.ymax); return env; diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java index 35e1676..c7e8578 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PointWrapper.java @@ -66,12 +66,16 @@ final class PointWrapper extends GeometryWithCRS<Shape> { } /** - * @return An empty envelope centered on this point. + * Returns an empty envelope centered on this point. */ @Override public GeneralEnvelope getEnvelope() { - final DirectPosition centroid = getCentroid(); - return new GeneralEnvelope(centroid, centroid); + final GeneralEnvelope env = createEnvelope(); + final double x = point.getX(); + final double y = point.getY(); + env.setRange(0, x, y); + env.setRange(1, x, y); + return env; } /** diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java index 8a6446d..c5980b5 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/Wrapper.java @@ -31,7 +31,6 @@ import org.apache.sis.internal.feature.GeometryWithCRS; import org.apache.sis.internal.referencing.j2d.ShapeUtilities; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Debug; -import org.opengis.referencing.crs.CoordinateReferenceSystem; /** @@ -74,16 +73,14 @@ final class Wrapper extends GeometryWithCRS<Shape> { } /** - * If the wrapped geometry is non-empty, returns its envelope as an Apache SIS implementation. - * Otherwise returns {@code null}. + * Returns the Java2D envelope as an Apache SIS implementation. * - * @return the envelope of the geometry, or {@code null} if empty. + * @return the envelope of the geometry. */ @Override public GeneralEnvelope getEnvelope() { final Rectangle2D bounds = geometry.getBounds2D(); - final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); - final GeneralEnvelope env = crs == null ? new GeneralEnvelope(Factory.BIDIMENSIONAL) : new GeneralEnvelope(crs); + final GeneralEnvelope env = createEnvelope(); env.setRange(0, bounds.getMinX(), bounds.getMaxX()); env.setRange(1, bounds.getMinY(), bounds.getMaxY()); return env; diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java index c3457bc..15e0b98 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/Wrapper.java @@ -36,6 +36,7 @@ import org.apache.sis.geometry.GeneralDirectPosition; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.internal.feature.Geometries; import org.apache.sis.internal.feature.GeometryWrapper; +import org.apache.sis.internal.referencing.ReferencingUtilities; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.Debug; @@ -101,18 +102,31 @@ final class Wrapper extends GeometryWrapper<Geometry> { */ @Override public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { - if (crs != null) ArgumentChecks.ensureBetween("CRS dimension", 2, 3, crs.getCoordinateSystem().getDimension()); + final int dimension = ReferencingUtilities.getDimension(crs); + if (dimension != Factory.BIDIMENSIONAL) { + ArgumentChecks.ensureDimensionMatches("crs", + (dimension <= Factory.BIDIMENSIONAL) ? Factory.BIDIMENSIONAL : 3, crs); + } JTS.setCoordinateReferenceSystem(geometry, crs); } /** - * - * @return the envelope of the decorated JTS geometry. Never null, but can be empty. + * Returns the envelope of the wrapped JTS geometry. Never null, but may be empty. + * In current implementation, <var>z</var> values of three-dimensional envelopes + * are {@link Double#NaN}. It may change in a future version if we have a way to + * get those <var>z</var> values from a JTS object. */ @Override public GeneralEnvelope getEnvelope() { final Envelope bounds = geometry.getEnvelopeInternal(); - final GeneralEnvelope env = new GeneralEnvelope(Factory.BIDIMENSIONAL); + final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); + final GeneralEnvelope env; + if (crs != null) { + env = new GeneralEnvelope(crs); + env.setToNaN(); + } else { + env = new GeneralEnvelope(Factory.BIDIMENSIONAL); + } env.setRange(0, bounds.getMinX(), bounds.getMaxX()); env.setRange(1, bounds.getMinY(), bounds.getMaxY()); return env; @@ -124,15 +138,20 @@ final class Wrapper extends GeometryWrapper<Geometry> { @Override public DirectPosition getCentroid() { final Coordinate c = geometry.getCentroid().getCoordinate(); - final double z = c.getZ(); - if (Double.isNaN(z)) { - return new DirectPosition2D(getCoordinateReferenceSystem(), c.x, c.y); - } else { - final GeneralDirectPosition point = new GeneralDirectPosition(c.x, c.y, z); - final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); - if (crs != null) point.setCoordinateReferenceSystem(crs); + final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); + if (crs == null) { + final double z = c.getZ(); + if (!Double.isNaN(z)) { + return new GeneralDirectPosition(c.x, c.y, z); + } + } else if (ReferencingUtilities.getDimension(crs) != Factory.BIDIMENSIONAL) { + final GeneralDirectPosition point = new GeneralDirectPosition(crs); + point.setOrdinate(0, c.x); + point.setOrdinate(1, c.y); + point.setOrdinate(2, c.getZ()); return point; } + return new DirectPosition2D(crs, c.x, c.y); } /** diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java index f74c902..91bde02 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java @@ -44,6 +44,7 @@ import static org.apache.sis.test.ReferencingAssert.*; * Tests {@link GridExtent}. * * @author Martin Desruisseaux (IRD, Geomatys) + * @author Alexis Manin (Geomatys) * @version 1.1 * @since 1.0 * @module @@ -315,56 +316,75 @@ public final strictfp class GridExtentTest extends TestCase { "Time: [ 40 … 49] (10 cells)\n", buffer); } + /** + * Verifies that a translation of zero cell results in the same {@link GridExtent} instance. + */ @Test public void empty_translation_returns_same_extent_instance() { final GridExtent extent = new GridExtent(10, 10); - assertTrue("Same instance returned in case of no-op", extent == extent.translate()); - assertTrue("Same instance returned in case of no-op", extent == extent.translate(null)); - assertTrue("Same instance returned in case of no-op", extent == extent.translate(new long[0])); - assertTrue("Same instance returned in case of no-op", extent == extent.translate(0)); - assertTrue("Same instance returned in case of no-op", extent == extent.translate(0, 0)); + assertSame("Same instance returned in case of no-op", extent, extent.translate(null)); + assertSame("Same instance returned in case of no-op", extent, extent.translate()); + assertSame("Same instance returned in case of no-op", extent, extent.translate(0)); + assertSame("Same instance returned in case of no-op", extent, extent.translate(0, 0)); } + /** + * Verifies that {@link GridExtent#translate(long...)} does not accept a vector with too many dimensions. + */ @Test public void translation_fails_if_given_array_contains_too_many_elements() { try { new GridExtent(2, 1).translate(2, 1, 2); - fail("A translation with too many dimensions should fail-fast"); + fail("A translation with too many dimensions shall fail."); } catch (IllegalArgumentException e) { - // expected behavior + assertTrue(e.getMessage().contains("translation")); + // Expected behavior. } } + /** + * Verifies that {@link GridExtent#translate(long...)} accepts a vector with less dimensions + * than the extent number of dimensions. No translation shall be applied in missing dimensions. + */ @Test public void translating_only_first_dimensions_leave_others_untouched() { - final GridExtent base = new GridExtent(null, 0, 0, 0, 2, 2, 2); + final GridExtent base = new GridExtent(null, new long[] { + 0, 0, 0, + 2, 2, 2 + }); final GridExtent translatedByX = base.translate(1); - assertArrayEquals("Lower corner", new long[] {1, 0, 0}, translatedByX.getLow().getCoordinateValues()); + assertArrayEquals("Lower corner", new long[] {1, 0, 0}, translatedByX.getLow() .getCoordinateValues()); assertArrayEquals("Upper corner", new long[] {3, 2, 2}, translatedByX.getHigh().getCoordinateValues()); final GridExtent translatedByY = base.translate(0, -1); - assertArrayEquals("Lower corner", new long[] {0, -1, 0}, translatedByY.getLow().getCoordinateValues()); - assertArrayEquals("Upper corner", new long[] {2, 1, 2}, translatedByY.getHigh().getCoordinateValues()); + assertArrayEquals("Lower corner", new long[] {0, -1, 0}, translatedByY.getLow() .getCoordinateValues()); + assertArrayEquals("Upper corner", new long[] {2, 1, 2}, translatedByY.getHigh().getCoordinateValues()); final GridExtent translatedByXAndY = base.translate(-1, 4); - assertArrayEquals("Lower corner", new long[] {-1, 4, 0}, translatedByXAndY.getLow().getCoordinateValues()); - assertArrayEquals("Upper corner", new long[] {1, 6, 2}, translatedByXAndY.getHigh().getCoordinateValues()); + assertArrayEquals("Lower corner", new long[] {-1, 4, 0}, translatedByXAndY.getLow() .getCoordinateValues()); + assertArrayEquals("Upper corner", new long[] { 1, 6, 2}, translatedByXAndY.getHigh().getCoordinateValues()); - // paranoid check: ensure base extent has been left untouched - assertArrayEquals("Base lower corner", new long[]{0, 0, 0}, base.getLow().getCoordinateValues()); - assertArrayEquals("Base lower corner", new long[]{2, 2, 2}, base.getHigh().getCoordinateValues()); + // Paranoiac check: ensure that base extent has been left untouched. + assertArrayEquals("Base lower corner", new long[] {0, 0, 0}, base.getLow() .getCoordinateValues()); + assertArrayEquals("Base lower corner", new long[] {2, 2, 2}, base.getHigh().getCoordinateValues()); } + /** + * Verifies that {@link GridExtent#translate(long...)} applies a translation on all dimensions + * when the given vector is long enough. + */ @Test public void translating_all_dimensions() { - final GridExtent base = new GridExtent(null, -1, -1, -2, 10, 2, 2, 2, 20); - + final GridExtent base = new GridExtent(null, new long[] { + -1, -1, -2, 10, + 2, 2, 2, 20 + }); final GridExtent translated = base.translate(-2, 1, 1, 100); - assertArrayEquals("Lower corner", new long[] {-3, 0, -1, 110}, translated.getLow().getCoordinateValues()); - assertArrayEquals("Upper corner", new long[] {0, 3, 3, 120}, translated.getHigh().getCoordinateValues()); + assertArrayEquals("Lower corner", new long[] {-3, 0, -1, 110}, translated.getLow() .getCoordinateValues()); + assertArrayEquals("Upper corner", new long[] { 0, 3, 3, 120}, translated.getHigh().getCoordinateValues()); - // paranoid check: ensure base extent has been left untouched - assertArrayEquals("Base lower corner", new long[]{-1, -1, -2, 10}, base.getLow().getCoordinateValues()); - assertArrayEquals("Base lower corner", new long[]{2, 2, 2, 20}, base.getHigh().getCoordinateValues()); + // Paranoiac check: ensure that base extent has been left untouched. + assertArrayEquals("Base lower corner", new long[] {-1, -1, -2, 10}, base.getLow() .getCoordinateValues()); + assertArrayEquals("Base lower corner", new long[] { 2, 2, 2, 20}, base.getHigh().getCoordinateValues()); } }
