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 22fb84d8e4b98514ab99d677c1bcd386af9029a7 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Aug 13 10:41:31 2022 +0200 Move isoline `Tracer.Polyline` to a top-level class `PolylineBuffer`. There is no code change other than this move and documentation updates. --- .../processing/isoline/PolylineBuffer.java | 215 +++++++++++++ .../sis/internal/processing/isoline/Tracer.java | 343 +++++---------------- 2 files changed, 294 insertions(+), 264 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java new file mode 100644 index 0000000000..384c765a17 --- /dev/null +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java @@ -0,0 +1,215 @@ +/* + * 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.internal.processing.isoline; + +import java.util.Arrays; +import java.awt.geom.Path2D; +import org.apache.sis.internal.feature.j2d.PathBuilder; +import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.Debug; + + +/** + * Coordinates of a polyline under construction. Coordinates can be appended in only one direction. + * If the polyline may growth on both directions (which happens if the polyline crosses the bottom + * side and the right side of a cell), then the two directions are handled by two distinct instances + * connected by their {@link #opposite} field. + * + * <p>When a polyline has been completed, its content is copied to {@link Tracer.Level#path} + * and the {@code PolylineBuffer} object is recycled for a new polyline.</p> + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.3 + * @since 1.1 + * @module + */ +final class PolylineBuffer { + /** + * Number of coordinates in a tuple. + */ + static final int DIMENSION = 2; + + /** + * Coordinates as (x,y) tuples. This array is expanded as needed. + */ + double[] coordinates; + + /** + * Number of valid elements in the {@link #coordinates} array. + * This is twice the number of points. + */ + int size; + + /** + * If the polyline has points added to its two extremities, the other extremity. Otherwise {@code null}. + * The first point of {@code opposite} polyline is connected to the first point of this polyline. + * Consequently when those two polylines are joined in a single polyline, the coordinates of either + * {@code this} or {@code opposite} must be iterated in reverse order. + */ + PolylineBuffer opposite; + + /** + * Creates an initially empty polyline. + */ + PolylineBuffer() { + coordinates = ArraysExt.EMPTY_DOUBLE; + } + + /** + * Creates a new polyline wrapping the given coordinates. Used for wrapping {@link Unclosed} + * instances in objects expected by {@link Tracer#writeTo(Joiner, Polyline[], boolean)}. + * Those {@code Polyline} instances are temporary. + */ + PolylineBuffer(final double[] data) { + coordinates = data; + size = data.length; + } + + /** + * Discards all coordinates in this polyline. This method does not clear + * the {@link #opposite} polyline; it is caller's responsibility to do so. + */ + final void clear() { + opposite = null; + size = 0; + } + + /** + * Returns whether this polyline is empty. This method is used only for {@code assert isEmpty()} + * statement because of the check for {@code opposite == null}: an empty polyline should not have + * a non-null {@link #opposite} value. + */ + final boolean isEmpty() { + return size == 0 & (opposite == null); + } + + /** + * Declares that the specified polyline will add points in the direction opposite to this polyline. + * This happens when the polyline crosses the bottom side and the right side of a cell (assuming an + * iteration from left to right and top to bottom). + * + * <p>This method is typically invoked in the following pattern (but this is not mandatory). + * An important aspect is that {@code this} and {@code other} should be on perpendicular axes:</p> + * + * {@preformat java + * interpolateOnBottomSide(polylinesOnTop[x].attach(polylineOnLeft)); + * } + * + * @return {@code this} for method calls chaining. + */ + final PolylineBuffer attach(final PolylineBuffer other) { + assert (opposite == null) & (other.opposite == null); + other.opposite = this; + opposite = other; + return this; + } + + /** + * Transfers all coordinates from given polylines to this polylines, in same order. + * This is used when polyline on the left side continues on bottom side, + * or conversely when polyline on the top side continues on right side. + * This polyline shall be empty before this method is invoked. + * The given source will become empty after this method returned. + * + * @param source the source from which to take data. + * @return {@code this} for method calls chaining. + */ + final PolylineBuffer transferFrom(final PolylineBuffer source) { + assert isEmpty(); + final double[] swap = coordinates; + coordinates = source.coordinates; + size = source.size; + opposite = source.opposite; + if (opposite != null) { + opposite.opposite = this; + } + source.clear(); + source.coordinates = swap; + return this; + } + + /** + * Transfers all coordinates from this polyline to the polyline going in opposite direction. + * This is used when this polyline reached the right image border, in which case its data + * will be lost if we do not copy them somewhere. + * + * @return {@code true} if coordinates have been transferred, + * or {@code false} if there is no opposite direction. + */ + final boolean transferToOpposite() { + if (opposite == null) { + return false; + } + final int sum = size + opposite.size; + double[] data = opposite.coordinates; + if (sum > data.length) { + data = new double[sum]; + } + System.arraycopy(opposite.coordinates, 0, data, size, opposite.size); + for (int i=0, t=size; (t -= DIMENSION) >= 0;) { + data[t ] = coordinates[i++]; + data[t+1] = coordinates[i++]; + } + opposite.size = sum; + opposite.coordinates = data; + opposite.opposite = null; + clear(); + return true; + } + + /** + * Appends given coordinates to this polyline. + * + * @param x first coordinate of the (x,y) tuple to add. + * @param y second coordinate of the (x,y) tuple to add. + */ + final void append(final double x, final double y) { + if (size >= coordinates.length) { + coordinates = Arrays.copyOf(coordinates, Math.max(Math.multiplyExact(size, 2), 32)); + } + coordinates[size++] = x; + coordinates[size++] = y; + } + + /** + * Returns a string representation of this {@code Polyline} for debugging purposes. + */ + @Override + public String toString() { + return PathBuilder.toString(coordinates, size); + } + + /** + * Appends the pixel coordinates of this polyline to the given path, for debugging purposes only. + * The {@link #gridToCRS} transform is <em>not</em> applied by this method. + * For avoiding confusing behavior, that transform should be null. + * + * @param appendTo where to append the coordinates. + * + * @see Tracer.Level#toRawPath(Path2D) + */ + @Debug + final void toRawPath(final Path2D appendTo) { + int i = 0; + if (i < size) { + appendTo.moveTo(coordinates[i++], coordinates[i++]); + while (i < size) { + appendTo.lineTo(coordinates[i++], coordinates[i++]); + } + } + } +} diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java index 3d6abbf6c2..c7b4bb564e 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java @@ -30,7 +30,6 @@ import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.apache.sis.internal.feature.j2d.PathBuilder; import org.apache.sis.internal.util.Numerics; -import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Debug; @@ -143,11 +142,11 @@ final class Tracer { * (2)╌╌╌(3) * } * - * Bits are set to 1 where the data value is above the isoline {@linkplain #value}, and 0 where the data - * value is below the isoline value. Data values exactly equal to the isoline value are handled as if - * they were greater. It does not matter for interpolations: we could flip this convention randomly, - * the interpolated points would still the same. It could change the way line segments are assembled in a - * single {@link Polyline}, but the algorithm stay consistent if we always apply the same rule for all points. + * Bits are set to 1 where the data value is above the isoline {@linkplain #value}, and 0 where the data value + * is below the isoline value. Data values exactly equal to the isoline value are handled as if they were greater. + * It does not matter for interpolations: we could flip this convention randomly, the interpolated points would + * still be the same. It could change the way line segments are assembled in a single {@link PolylineBuffer}, + * but the algorithm stay consistent if we always apply the same rule for all points. * * <h4>Reusing bits from previous iteration</h4> * We will iterate on pixels from left to right, then from top to bottom. With that iteration order, @@ -172,7 +171,7 @@ final class Tracer { * ○╌╌╌╌╌╌○ ╱●╌╌╌╌╱╌○ ○╌╌╌╌╌╌○╲ * } * - * This field {@linkplain Polyline#isEmpty() is empty} if the cell in previous iteration was like below + * This field {@link PolylineBuffer#isEmpty() is empty} if the cell in previous iteration was like below * (no line cross the right border): * * {@preformat text @@ -182,7 +181,7 @@ final class Tracer { * ○╌╌╌╌╲╌● ○╌╌╌┼╌╌● * } */ - private final Polyline polylineOnLeft; + private final PolylineBuffer polylineOnLeft; /** * The polylines in each column which need to be continued on the next row. @@ -202,13 +201,14 @@ final class Tracer { * x coordinate of first pixel (upper-left corner) * } */ - private final Polyline[] polylinesOnTop; + private final PolylineBuffer[] polylinesOnTop; /** - * Paths that have not yet been closed. The {@link Polyline} coordinates are copied in this map when iteration - * finished on a row and the polyline is not reused by next row, or when the {@link #closeLeftWithTop(Polyline)} - * method has been invoked but the geometry to close is still not complete. This map accumulates those partial - * shapes for assembling them later when missing parts become available. + * Paths that have not yet been closed. The {@link PolylineBuffer} coordinates are copied in this map when + * iteration finished on a row but the polyline under construction will not be continued by the next row, + * or when the {@link #closeLeftWithTop(PolylineBuffer)} method has been invoked but the geometry to close + * is still not complete. This map accumulates those partial shapes for assembling them later when missing + * parts become available. * * <h4>Map keys</h4> * Keys are grid coordinates rounded toward 0. The coordinate having fraction digits has its bits inverted @@ -220,15 +220,15 @@ final class Tracer { * in reverse order and all {@code double[]} arrays at odd indices shall have their points read in forward order. * The list may contain null elements when there is no data in the corresponding iteration order. * - * @see #closeLeftWithTop(Polyline) + * @see #closeLeftWithTop(PolylineBuffer) */ private final Map<Point,Unclosed> partialPaths; /** * Builder of isolines as a Java2D shape, created when first needed. - * The {@link Polyline} coordinates are copied in this path when a geometry is closed. + * The {@link PolylineBuffer} coordinates are copied in this path when a geometry is closed. * - * @see #writeTo(Joiner, Polyline[], boolean) + * @see #writeTo(Joiner, PolylineBuffer[], boolean) */ private Joiner path; @@ -249,10 +249,10 @@ final class Tracer { this.band = band; this.value = value; partialPaths = new HashMap<>(); - polylineOnLeft = new Polyline(); - polylinesOnTop = new Polyline[width]; + polylineOnLeft = new PolylineBuffer(); + polylinesOnTop = new PolylineBuffer[width]; for (int i=0; i<width; i++) { - polylinesOnTop[i] = new Polyline(); + polylinesOnTop[i] = new PolylineBuffer(); } } @@ -341,7 +341,7 @@ final class Tracer { case UPPER_RIGHT | LOWER_RIGHT: case UPPER_LEFT | LOWER_LEFT: { assert polylineOnLeft.isEmpty(); - final Polyline polylineOnTop = polylinesOnTop[x]; + final PolylineBuffer polylineOnTop = polylinesOnTop[x]; interpolateMissingTopSide(polylineOnTop); interpolateOnBottomSide(polylineOnTop); // Will be top side of next row. break; @@ -423,14 +423,14 @@ final class Tracer { } boolean LLtoUR = isDataAbove == (LOWER_LEFT | UPPER_RIGHT); LLtoUR ^= (average <= value); - final Polyline polylineOnTop = polylinesOnTop[x]; + final PolylineBuffer polylineOnTop = polylinesOnTop[x]; if (LLtoUR) { closeLeftWithTop(polylineOnTop); interpolateOnRightSide(); interpolateOnBottomSide(polylineOnTop.attach(polylineOnLeft)); } else { interpolateMissingLeftSide(); - final Polyline swap = new Polyline().transferFrom(polylineOnTop); + final PolylineBuffer swap = new PolylineBuffer().transferFrom(polylineOnTop); interpolateOnBottomSide(polylineOnTop.transferFrom(polylineOnLeft)); interpolateMissingTopSide(polylineOnLeft.transferFrom(swap)); interpolateOnRightSide(); @@ -455,7 +455,7 @@ final class Tracer { * Appends to {@code polylineOnTop} a point interpolated on the top side if that point is missing. * This interpolation should happens only in the first row. */ - private void interpolateMissingTopSide(final Polyline polylineOnTop) { + private void interpolateMissingTopSide(final PolylineBuffer polylineOnTop) { if (polylineOnTop.size == 0) { interpolateOnTopSide(polylineOnTop); } @@ -464,7 +464,7 @@ final class Tracer { /** * Appends to the given polyline a point interpolated on the top side. */ - private void interpolateOnTopSide(final Polyline appendTo) { + private void interpolateOnTopSide(final PolylineBuffer appendTo) { appendTo.append(translateX + (x + interpolate(0, pixelStride)), translateY + (y)); } @@ -482,7 +482,7 @@ final class Tracer { * Appends to the given polyline a point interpolated on the bottom side. * The polyline on top side will become a {@code polylineOnBottoù} in next row. */ - private void interpolateOnBottomSide(final Polyline polylineOnTop) { + private void interpolateOnBottomSide(final PolylineBuffer polylineOnTop) { polylineOnTop.append(translateX + (x + interpolate(2*pixelStride, 3*pixelStride)), translateY + (y + 1)); } @@ -503,10 +503,11 @@ final class Tracer { } /** - * Joins {@link #polylineOnLeft} with {@code polylineOnTop}, saves their coordinates and clear - * those {@code Polyline} instances for use in next cell. The coordinates are written directly - * to {@link #path} if we got a closed polygon, or otherwise are saved in {@link #partialPaths} - * for later processing. This method is invoked for cells like below: + * Joins {@link #polylineOnLeft} with {@code polylineOnTop}, saves their coordinates + * and clear those {@link PolylineBuffer} instances for use in next cell. + * The coordinates are written directly to {@link #path} if we got a closed polygon, + * or otherwise are saved in {@link #partialPaths} for later processing. + * This method is invoked for cells like below: * * {@preformat text * ●╌╱╌╌╌╌○ ○╌╱╌╌╌╌● ○╌╱╌╌╌╌●╱ @@ -521,18 +522,18 @@ final class Tracer { * @param polylineOnTop value of {@code polylinesOnTop[x]}. * @throws TransformException if the {@link Tracer#gridToCRS} transform can not be applied. */ - private void closeLeftWithTop(final Polyline polylineOnTop) throws TransformException { + private void closeLeftWithTop(final PolylineBuffer polylineOnTop) throws TransformException { interpolateMissingLeftSide(); interpolateMissingTopSide(polylineOnTop); - final Polyline[] polylines; + final PolylineBuffer[] polylines; if (polylineOnLeft.opposite == polylineOnTop) { assert polylineOnTop.opposite == polylineOnLeft; /* * We have a loop: the polygon can be closed now, without copying coordinates to temporary buffers. - * Points in the two `Polyline` instances will be iterated in (reverse, forward) order respectively. + * Points in `PolylineBuffer` instances will be iterated in (reverse, forward) order respectively. * Consequently the points we just interpolated will be first point and last point before closing. */ - polylines = new Polyline[] {polylineOnTop, polylineOnLeft}; // (reverse, forward) point order. + polylines = new PolylineBuffer[] {polylineOnTop, polylineOnLeft}; // (reverse, forward) point order. } else { /* * Joining left and top polylines do not yet create a closed shape. Consequently we may not write @@ -544,7 +545,7 @@ final class Tracer { * Fragment starts and ends with NaN values. We will not be able to complete a polygon. * Better to write the polylines now for avoiding temporary copies of their coordinates. */ - polylines = new Polyline[] { + polylines = new PolylineBuffer[] { polylineOnLeft.opposite, polylineOnLeft, polylineOnTop, polylineOnTop.opposite }; } else if (fragment.addOrMerge(partialPaths)) { @@ -565,13 +566,13 @@ final class Tracer { * Writes the content of given polyline without closing it as a polygon. * The given polyline will become empty after this method call. */ - private void writeUnclosed(final Polyline polyline) throws TransformException { + private void writeUnclosed(final PolylineBuffer polyline) throws TransformException { final Unclosed fragment = new Unclosed(polyline, null); - final Polyline[] polylines; + final PolylineBuffer[] polylines; final boolean close; if (fragment.isEmpty()) { close = false; - polylines = new Polyline[] {polyline.opposite, polyline}; // (reverse, forward) point order. + polylines = new PolylineBuffer[] {polyline.opposite, polyline}; // (reverse, forward) point order. } else { close = fragment.addOrMerge(partialPaths); if (!close) { @@ -586,7 +587,7 @@ final class Tracer { /** * Invoked after iteration on a single row has been completed. If there is a polyline * finishing on the right image border, the coordinates needs to be saved somewhere - * because that {@code Polyline} will not be continued by cells on next rows. + * because that {@link PolylineBuffer} will not be continued by cells on next rows. */ final void finishedRow() throws TransformException { if (!polylineOnLeft.transferToOpposite()) { @@ -599,7 +600,7 @@ final class Tracer { * Invoked after the iteration has been completed on the full area of interest. * This method writes all remaining polylines to {@link #partialPaths}. * It assumes that {@link #finishedRow()} has already been invoked. - * This {@code Isoline} can not be used anymore after this call. + * This {@link Level} instance can not be used anymore after this call. */ final void finish() throws TransformException { assert polylineOnLeft.isEmpty(); @@ -695,201 +696,15 @@ final class Tracer { final Shape s = (path != null) ? path.build() : shape; if (s != null) appendTo.append(s, false); polylineOnLeft.toRawPath(appendTo); - for (final Polyline p : polylinesOnTop) { + for (final PolylineBuffer p : polylinesOnTop) { if (p != null) p.toRawPath(appendTo); } } } /** - * Coordinates of a polyline under construction. Coordinates can be appended in only one direction. - * If the polyline may growth on both directions (which happens if the polyline crosses the bottom - * side and the right side of a cell), then the two directions are handled by two distinct instances - * connected by their {@link #opposite} field. - * - * <p>When a polyline has been completed, its content is copied to {@link Level#path} - * and the {@code Polyline} object is recycled for a new polyline.</p> - */ - private static final class Polyline { - /** - * Number of coordinates in a tuple. - */ - static final int DIMENSION = 2; - - /** - * Coordinates as (x,y) tuples. This array is expanded as needed. - */ - double[] coordinates; - - /** - * Number of valid elements in the {@link #coordinates} array. - * This is twice the number of points. - */ - int size; - - /** - * If the polyline has points added to its two extremities, the other extremity. Otherwise {@code null}. - * The first point of {@code opposite} polyline is connected to the first point of this polyline. - * Consequently when those two polylines are joined in a single polyline, the coordinates of either - * {@code this} or {@code opposite} must be iterated in reverse order. - */ - Polyline opposite; - - /** - * Creates an initially empty polyline. - */ - Polyline() { - coordinates = ArraysExt.EMPTY_DOUBLE; - } - - /** - * Creates a new polyline wrapping the given coordinates. Used for wrapping {@link Unclosed} - * instances in objects expected by {@link Tracer#writeTo(Joiner, Polyline[], boolean)}. - * Those {@code Polyline} instances are temporary. - */ - Polyline(final double[] data) { - coordinates = data; - size = data.length; - } - - /** - * Discards all coordinates in this polyline. This method does not clear - * the {@link #opposite} polyline; it is caller's responsibility to do so. - */ - final void clear() { - opposite = null; - size = 0; - } - - /** - * Returns whether this polyline is empty. This method is used only for {@code assert isEmpty()} - * statement because of the check for {@code opposite == null}: an empty polyline should not have - * a non-null {@link #opposite} value. - */ - final boolean isEmpty() { - return size == 0 & (opposite == null); - } - - /** - * Declares that the specified polyline will add points in the direction opposite to this polyline. - * This happens when the polyline crosses the bottom side and the right side of a cell (assuming an - * iteration from left to right and top to bottom). - * - * <p>This method is typically invoked in the following pattern (but this is not mandatory). - * An important aspect is that {@code this} and {@code other} should be on perpendicular axes:</p> - * - * {@preformat java - * interpolateOnBottomSide(polylinesOnTop[x].attach(polylineOnLeft)); - * } - * - * @return {@code this} for method calls chaining. - */ - final Polyline attach(final Polyline other) { - assert (opposite == null) & (other.opposite == null); - other.opposite = this; - opposite = other; - return this; - } - - /** - * Transfers all coordinates from given polylines to this polylines, in same order. - * This is used when polyline on the left side continues on bottom side, - * or conversely when polyline on the top side continues on right side. - * This polyline shall be empty before this method is invoked. - * The given source will become empty after this method returned. - * - * @param source the source from which to take data. - * @return {@code this} for method calls chaining. - */ - final Polyline transferFrom(final Polyline source) { - assert isEmpty(); - final double[] swap = coordinates; - coordinates = source.coordinates; - size = source.size; - opposite = source.opposite; - if (opposite != null) { - opposite.opposite = this; - } - source.clear(); - source.coordinates = swap; - return this; - } - - /** - * Transfers all coordinates from this polyline to the polyline going in opposite direction. - * This is used when this polyline reached the right image border, in which case its data - * will be lost if we do not copy them somewhere. - * - * @return {@code true} if coordinates have been transferred, - * or {@code false} if there is no opposite direction. - */ - final boolean transferToOpposite() { - if (opposite == null) { - return false; - } - final int sum = size + opposite.size; - double[] data = opposite.coordinates; - if (sum > data.length) { - data = new double[sum]; - } - System.arraycopy(opposite.coordinates, 0, data, size, opposite.size); - for (int i=0, t=size; (t -= DIMENSION) >= 0;) { - data[t ] = coordinates[i++]; - data[t+1] = coordinates[i++]; - } - opposite.size = sum; - opposite.coordinates = data; - opposite.opposite = null; - clear(); - return true; - } - - /** - * Appends given coordinates to this polyline. - * - * @param x first coordinate of the (x,y) tuple to add. - * @param y second coordinate of the (x,y) tuple to add. - */ - final void append(final double x, final double y) { - if (size >= coordinates.length) { - coordinates = Arrays.copyOf(coordinates, Math.max(Math.multiplyExact(size, 2), 32)); - } - coordinates[size++] = x; - coordinates[size++] = y; - } - - /** - * Returns a string representation of this {@code Polyline} for debugging purposes. - */ - @Override - public String toString() { - return PathBuilder.toString(coordinates, size); - } - - /** - * Appends the pixel coordinates of this polyline to the given path, for debugging purposes only. - * The {@link #gridToCRS} transform is <em>not</em> applied by this method. - * For avoiding confusing behavior, that transform should be null. - * - * @param appendTo where to append the coordinates. - * - * @see Level#toRawPath(Path2D) - */ - @Debug - private void toRawPath(final Path2D appendTo) { - int i = 0; - if (i < size) { - appendTo.moveTo(coordinates[i++], coordinates[i++]); - while (i < size) { - appendTo.lineTo(coordinates[i++], coordinates[i++]); - } - } - } - } - - /** - * List of {@code Polyline} coordinates that have not yet been closed. Each {@code double[]} in this list is - * a copy of a {@code Polyline} used by {@link Level}. Those copies are performed for saving data before they + * List of {@code PolylineBuffer} coordinates that have not yet been closed. Each {@code double[]} in this list is + * a copy of a {@link PolylineBuffer} used by {@link Level}. Those copies are performed for saving data before they * are overwritten by next iterated cell. * * <h2>List indices and ordering of points</h2> @@ -918,9 +733,9 @@ final class Tracer { * @param polylineOnLeft first polyline with points in forward order. Shall not be null. * @param polylineOnTop next polyline with points in reverse order, or {@code null} if none. */ - Unclosed(final Polyline polylineOnLeft, final Polyline polylineOnTop) { + Unclosed(final PolylineBuffer polylineOnLeft, final PolylineBuffer polylineOnTop) { /* - * Search for first point and last point by inspecting `Polyline`s in the order shown below. + * Search for first and last point by inspecting `PolylineBuffer` instances in the order shown below. * The first 4 rows and the last 4 rows search for first point and last point respectively. * The empty rows in the middle are an intentional gap for creating a regular pattern that * we can exploit for 3 decisions that need to be done during the loop: @@ -929,7 +744,7 @@ final class Tracer { * ✓ (index % 3) = 0 if using `opposite` value of polyline (may be null). * ✓ (index & 1) = 0 if fetching last point (otherwise fetch first point). * - * Index Polyline (iteration order) !(i & 2) !(i % 3) !(i & 1) Comment + * Index PolylineBuffer (order) !(i & 2) !(i % 3) !(i & 1) Comment * ──────────────────────────────────────────────────────────────────────────── * [0] polylineOnLeft.opposite (←) ✓ ✓ ✓ (1) * [1] polylineOnLeft (→) ✓ (2) @@ -943,15 +758,15 @@ final class Tracer { * [9] polylineOnLeft.opposite (←) ✓ ✓ (4) * * Comments: - * (1) Last `Polyline` point is first `Unclosed` point because of reverse iteration order. - * (2) First `Polyline` point is first `Unclosed` point because of forward iteration order. - * (3) Last `Polyline` point is last `Unclosed` point because of forward iteration order. - * (4) First `Polyline` point is last `Unclosed` point because of reverse iteration order. + * (1) Last `PolylineBuffer` point is first `Unclosed` point because of reverse iteration order. + * (2) First `PolylineBuffer` point is first `Unclosed` point because of forward iteration order. + * (3) Last `PolylineBuffer` point is last `Unclosed` point because of forward iteration order. + * (4) First `PolylineBuffer` point is last `Unclosed` point because of reverse iteration order. */ int index = 0; do { - Polyline polyline = ((index & 2) == 0) ? polylineOnLeft : polylineOnTop; // See above table (column 4). - if (index % 3 == 0 && polyline != null) polyline = polyline.opposite; // See above table (column 5). + PolylineBuffer polyline = ((index & 2) == 0) ? polylineOnLeft : polylineOnTop; // See above table (column 4). + if (index % 3 == 0 && polyline != null) polyline = polyline.opposite; // See above table (column 5). if (polyline != null) { int n = polyline.size; if (n != 0) { @@ -1006,7 +821,7 @@ final class Tracer { take(polylineOnLeft.opposite); // Point will be iterated in reverse order. take(polylineOnLeft); // Point will be iterated in forward order. if (polylineOnTop != null) { - Polyline suffix = polylineOnTop.opposite; + PolylineBuffer suffix = polylineOnTop.opposite; take(polylineOnTop); // Inverse order. Set `polylineOnTop.opposite` to null. take(suffix); // Forward order. } @@ -1015,7 +830,7 @@ final class Tracer { /** * Takes a copy of coordinate values of given polyline, then clears that polyline. */ - private void take(final Polyline polyline) { + private void take(final PolylineBuffer polyline) { if (polyline != null && polyline.size != 0) { add(Arrays.copyOf(polyline.coordinates, polyline.size)); polyline.clear(); @@ -1110,17 +925,17 @@ final class Tracer { } /** - * Returns the content of this list as an array of {@link Polyline} instances. - * {@code Polyline} instances at even index should be written with their points in reverse order. + * Returns the content of this list as an array of {@link PolylineBuffer} instances. + * {@code PolylineBuffer} instances at even index should be written with their points in reverse order. * - * @see #writeTo(Joiner, Polyline[], boolean) + * @see #writeTo(Joiner, PolylineBuffer[], boolean) */ - final Polyline[] toPolylines() { - final Polyline[] polylines = new Polyline[size()]; + final PolylineBuffer[] toPolylines() { + final PolylineBuffer[] polylines = new PolylineBuffer[size()]; for (int i=0; i<polylines.length; i++) { final double[] coordinates = get(i); if (coordinates != null) { - polylines[i] = new Polyline(coordinates); + polylines[i] = new PolylineBuffer(coordinates); } } return polylines; @@ -1128,9 +943,9 @@ final class Tracer { } /** - * Assembles arbitrary amount of {@link Polyline}s in a single Java2D {@link Shape} for a specific isoline level. - * This class extends {@link PathBuilder} with two additional features: remove spikes caused by ambiguities, then - * apply a {@link MathTransform} on all coordinate values. + * Assembles arbitrary amount of {@link PolylineBuffer}s in a single Java2D {@link Shape} for an isoline level. + * This class extends {@link PathBuilder} with two additional features: remove spikes caused by ambiguities, + * then apply a {@link MathTransform} on all coordinate values. * * <h2>Spikes</h2> * If the shape delimited by given polylines has a part with zero width or height ({@literal i.e.} a spike), @@ -1164,9 +979,9 @@ final class Tracer { * </ul> * * This class detects and removes those spikes for avoiding convention-dependent results. - * We assume that spikes can appear only at the junction between two {@link Polyline} instances. + * We assume that spikes can appear only at the junction between two {@link PolylineBuffer} instances. * Rational: having a spike require that we move forward then backward on the same coordinates, - * which is possible only with a non-null {@link Polyline#opposite} field. + * which is possible only with a non-null {@link PolylineBuffer#opposite} field. */ private static final class Joiner extends PathBuilder { /** @@ -1186,10 +1001,10 @@ final class Tracer { * See {@link Joiner} class-javadoc for a description of the problem. * * <p>We perform the analysis in this method instead of in {@link #filterFull(double[], int)} on the - * the assumption that spikes can appear only between two calls to {@code append(…)} (because having a - * spike require that we move forward then backward on the same coordinates, which happen only with two - * distinct {@link Polyline} instances). It reduce the amount of coordinates to examine since we can check - * only the extremities instead of looking for spikes anywhere in the array.</p> + * the assumption that spikes can appear only between two calls to {@code append(…)} (because having + * a spike requires that we move forward then backward on the same coordinates, which happen only with + * two distinct {@link PolylineBuffer} instances). It reduce the amount of coordinates to examine since + * we can check only the extremities instead of looking for spikes anywhere in the array.</p> * * @param coordinates the coordinates to filter. Values can be modified in-place. * @param lower index of first coordinate to filter. Always even. @@ -1209,8 +1024,8 @@ final class Tracer { if (coordinates[spike1++] != xo) equalityMask &= ~1; if (coordinates[spike1++] != yo) equalityMask &= ~2; if (equalityMask == 0) { - equalityMask = before; // For keeping same search criterion. - spike1 -= Polyline.DIMENSION; // Restore previous position before mismatch. + equalityMask = before; // For keeping same search criterion. + spike1 -= PolylineBuffer.DIMENSION; // Restore previous position before mismatch. break; } } @@ -1218,7 +1033,7 @@ final class Tracer { if (coordinates[--spike0] != yo) equalityMask &= ~2; if (coordinates[--spike0] != xo) equalityMask &= ~1; if (equalityMask == 0) { - spike0 += Polyline.DIMENSION; + spike0 += PolylineBuffer.DIMENSION; break; } } @@ -1237,7 +1052,7 @@ final class Tracer { */ final int limit = spike1; int base; - while ((base = spike0 + 2*Polyline.DIMENSION) < limit) { // Spikes exist only with at least 3 points. + while ((base = spike0 + 2*PolylineBuffer.DIMENSION) < limit) { // Spikes exist only with at least 3 points. final double xo = coordinates[spike0++]; final double yo = coordinates[spike0++]; spike1 = limit; @@ -1250,7 +1065,7 @@ final class Tracer { System.arraycopy(coordinates, spike1, coordinates, spike0, upper - spike1); return upper - (spike1 - spike0); } - } while ((spike1 -= Polyline.DIMENSION) > base); + } while ((spike1 -= PolylineBuffer.DIMENSION) > base); } return upper; // Nothing to remove. } @@ -1262,15 +1077,15 @@ final class Tracer { @Override protected int filterFull(final double[] coordinates, final int upper) throws TransformException { if (gridToCRS != null) { - gridToCRS.transform(coordinates, 0, coordinates, 0, upper / Polyline.DIMENSION); + gridToCRS.transform(coordinates, 0, coordinates, 0, upper / PolylineBuffer.DIMENSION); } return upper; } } /** - * Writes all given polylines to the specified path builder. Null {@code Polyline} instances are ignored. - * {@code Polyline} instances at even index are written with their points in reverse order. + * Writes all given polylines to the specified path builder. Null {@code PolylineBuffer} instances are ignored. + * {@code PolylineBuffer} instances at even index are written with their points in reverse order. * All given polylines are cleared by this method. * * @param path where to write the polylines, or {@code null} if not yet created. @@ -1279,9 +1094,9 @@ final class Tracer { * @return the given path builder, or a newly created builder if the argument was null. * @throws TransformException if the {@link #gridToCRS} transform can not be applied. */ - private Joiner writeTo(Joiner path, final Polyline[] polylines, final boolean close) throws TransformException { + private Joiner writeTo(Joiner path, final PolylineBuffer[] polylines, final boolean close) throws TransformException { for (int pi=0; pi < polylines.length; pi++) { - final Polyline p = polylines[pi]; + final PolylineBuffer p = polylines[pi]; if (p == null) { continue; }