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 a44364b67c38da390dbb94a3dbed715f8affbe2d Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sun Aug 14 10:55:13 2022 +0200 Move the `Joiner` inner class to a top-level class. There is no code change (other than move) in this commit. --- .../sis/internal/processing/isoline/Fragments.java | 7 +- .../sis/internal/processing/isoline/Joiner.java | 168 +++++++++++++++++++++ .../sis/internal/processing/isoline/Tracer.java | 141 ----------------- 3 files changed, 172 insertions(+), 144 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Fragments.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Fragments.java index da97430340..698fc1b247 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Fragments.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Fragments.java @@ -37,11 +37,12 @@ import org.apache.sis.internal.util.Numerics; * of {@code double[]} arrays: because even indices become odd and odd indices become even, points order are * implicitly reverted without the need to rewrite all {@code double[]} array contents. * - * @see Tracer.Level#partialPaths - * * @author Martin Desruisseaux (Geomatys) * @version 1.3 - * @since 1.1 + * + * @see Tracer.Level#partialPaths + * + * @since 1.1 * @module */ @SuppressWarnings({"CloneableImplementsClone", "serial"}) // Not intended to be cloned or serialized. diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Joiner.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Joiner.java new file mode 100644 index 0000000000..b9ccfcfe87 --- /dev/null +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Joiner.java @@ -0,0 +1,168 @@ +/* + * 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 org.apache.sis.internal.feature.j2d.PathBuilder; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + + +/** + * 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), + * truncates the polylines for removing that spike. This situation happens when some pixel values are exactly + * equal to isoline value, as in the picture below: + * + * {@preformat text + * ●╌╌╌╲╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○ + * ╎ ╲ ╎ ╎ ╎ ╎ + * ╎ ╲╎ ╎ → ╎ ╎ + * ●╌╌╌╌╌╌●──────●──────●⤸╌╌╌╌╌○ + * ╎ ╱╎ ╎ ← ╎ ╎ + * ╎ ╱ ╎ ╎ ╎ ╎ + * ●╌╌╌╱╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○ + * } + * + * The spike may appear or not depending on the convention adopted for strictly equal values. + * In above picture, the spike appears because the convention used in this implementation is: + * + * <ul> + * <li>○: {@literal pixel value < isoline value}.</li> + * <li>●: {@literal pixel value ≥ isoline value}.</li> + * </ul> + * + * If the following convention was used instead, the spike would not appear in above figure + * (but would appear in different situations): + * + * <ul> + * <li>○: {@literal pixel value ≤ isoline value}.</li> + * <li>●: {@literal pixel value > isoline value}.</li> + * </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 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 PolylineBuffer#opposite} field. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.3 + * @since 1.1 + * @module + */ +final class Joiner extends PathBuilder { + /** + * Final transform to apply on coordinates, or {@code null} if none. + */ + private final MathTransform gridToCRS; + + /** + * Creates an initially empty set of isoline shapes. + */ + Joiner(final MathTransform gridToCRS) { + this.gridToCRS = gridToCRS; + } + + /** + * Detects and removes spikes for avoiding convention-dependent results. + * 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 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. + * @param upper index after the last coordinate to filter. Always even. + * @return number of valid coordinates after filtering. + */ + @Override + protected int filterChunk(final double[] coordinates, final int lower, final int upper) { + int spike0 = lower; // Will be index where (x,y) become different than (xo,yo). + int spike1 = lower; // Idem, but searching forward instead of backward. + if (spike1 < upper) { + final double xo = coordinates[spike1++]; + final double yo = coordinates[spike1++]; + int equalityMask = 3; // Bit 1 set if (x == xo), bit 2 set if (y == yo). + while (spike1 < upper) { + final int before = equalityMask; + if (coordinates[spike1++] != xo) equalityMask &= ~1; + if (coordinates[spike1++] != yo) equalityMask &= ~2; + if (equalityMask == 0) { + equalityMask = before; // For keeping same search criterion. + spike1 -= PolylineBuffer.DIMENSION; // Restore previous position before mismatch. + break; + } + } + while (spike0 > 0) { + if (coordinates[--spike0] != yo) equalityMask &= ~2; + if (coordinates[--spike0] != xo) equalityMask &= ~1; + if (equalityMask == 0) { + spike0 += PolylineBuffer.DIMENSION; + break; + } + } + } + /* + * Here we have range of indices where the polygon has a width or height of zero. + * Search for a common point, then truncate at that point. Indices are like below: + * + * 0 spike0 lower spike1 upper + * ●────●────●────●────●────●────●────●────●────●────● + * └╌╌╌╌remove╌╌╌╌┘ + * where: + * - `lower` and `spike0` are inclusive. + * - `upper` and `spike1` are exclusive. + * - the region to remove are sowewhere between `spike0` and `spike1`. + */ + final int limit = spike1; + int base; + 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; + do { + if (coordinates[spike1 - 2] == xo && coordinates[spike1 - 1] == yo) { + /* + * Remove points between the common point (xo,yo). The common point is kept on the + * left side (`spike0` is already after that point) and removed on the right side. + */ + System.arraycopy(coordinates, spike1, coordinates, spike0, upper - spike1); + return upper - (spike1 - spike0); + } + } while ((spike1 -= PolylineBuffer.DIMENSION) > base); + } + return upper; // Nothing to remove. + } + + /** + * Applies user-specified coordinate transform on all points of the whole polyline. + * This method is invoked after {@link #filterChunk(double[], int, int)}. + */ + @Override + protected int filterFull(final double[] coordinates, final int upper) throws TransformException { + if (gridToCRS != null) { + gridToCRS.transform(coordinates, 0, coordinates, 0, upper / PolylineBuffer.DIMENSION); + } + return upper; + } +} 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 17c777e460..5bfbbac58d 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 @@ -698,147 +698,6 @@ final class Tracer { } } - /** - * 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), - * truncates the polylines for removing that spike. This situation happens when some pixel values are exactly - * equal to isoline value, as in the picture below: - * - * {@preformat text - * ●╌╌╌╲╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○ - * ╎ ╲ ╎ ╎ ╎ ╎ - * ╎ ╲╎ ╎ → ╎ ╎ - * ●╌╌╌╌╌╌●──────●──────●⤸╌╌╌╌╌○ - * ╎ ╱╎ ╎ ← ╎ ╎ - * ╎ ╱ ╎ ╎ ╎ ╎ - * ●╌╌╌╱╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○╌╌╌╌╌╌○ - * } - * - * The spike may appear or not depending on the convention adopted for strictly equal values. - * In above picture, the spike appears because the convention used in this implementation is: - * - * <ul> - * <li>○: {@literal pixel value < isoline value}.</li> - * <li>●: {@literal pixel value ≥ isoline value}.</li> - * </ul> - * - * If the following convention was used instead, the spike would not appear in above figure - * (but would appear in different situations): - * - * <ul> - * <li>○: {@literal pixel value ≤ isoline value}.</li> - * <li>●: {@literal pixel value > isoline value}.</li> - * </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 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 PolylineBuffer#opposite} field. - */ - private static final class Joiner extends PathBuilder { - /** - * Final transform to apply on coordinates, or {@code null} if none. - */ - private final MathTransform gridToCRS; - - /** - * Creates an initially empty set of isoline shapes. - */ - Joiner(final MathTransform gridToCRS) { - this.gridToCRS = gridToCRS; - } - - /** - * Detects and removes spikes for avoiding convention-dependent results. - * 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 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. - * @param upper index after the last coordinate to filter. Always even. - * @return number of valid coordinates after filtering. - */ - @Override - protected int filterChunk(final double[] coordinates, final int lower, final int upper) { - int spike0 = lower; // Will be index where (x,y) become different than (xo,yo). - int spike1 = lower; // Idem, but searching forward instead of backward. - if (spike1 < upper) { - final double xo = coordinates[spike1++]; - final double yo = coordinates[spike1++]; - int equalityMask = 3; // Bit 1 set if (x == xo), bit 2 set if (y == yo). - while (spike1 < upper) { - final int before = equalityMask; - if (coordinates[spike1++] != xo) equalityMask &= ~1; - if (coordinates[spike1++] != yo) equalityMask &= ~2; - if (equalityMask == 0) { - equalityMask = before; // For keeping same search criterion. - spike1 -= PolylineBuffer.DIMENSION; // Restore previous position before mismatch. - break; - } - } - while (spike0 > 0) { - if (coordinates[--spike0] != yo) equalityMask &= ~2; - if (coordinates[--spike0] != xo) equalityMask &= ~1; - if (equalityMask == 0) { - spike0 += PolylineBuffer.DIMENSION; - break; - } - } - } - /* - * Here we have range of indices where the polygon has a width or height of zero. - * Search for a common point, then truncate at that point. Indices are like below: - * - * 0 spike0 lower spike1 upper - * ●────●────●────●────●────●────●────●────●────●────● - * └╌╌╌╌remove╌╌╌╌┘ - * where: - * - `lower` and `spike0` are inclusive. - * - `upper` and `spike1` are exclusive. - * - the region to remove are sowewhere between `spike0` and `spike1`. - */ - final int limit = spike1; - int base; - 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; - do { - if (coordinates[spike1 - 2] == xo && coordinates[spike1 - 1] == yo) { - /* - * Remove points between the common point (xo,yo). The common point is kept on the - * left side (`spike0` is already after that point) and removed on the right side. - */ - System.arraycopy(coordinates, spike1, coordinates, spike0, upper - spike1); - return upper - (spike1 - spike0); - } - } while ((spike1 -= PolylineBuffer.DIMENSION) > base); - } - return upper; // Nothing to remove. - } - - /** - * Applies user-specified coordinate transform on all points of the whole polyline. - * This method is invoked after {@link #filterChunk(double[], int, int)}. - */ - @Override - protected int filterFull(final double[] coordinates, final int upper) throws TransformException { - if (gridToCRS != null) { - gridToCRS.transform(coordinates, 0, coordinates, 0, upper / PolylineBuffer.DIMENSION); - } - return upper; - } - } - /** * 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.