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 d81b368aef7ff466c09a9e4f1ac84af652a036c5 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Aug 13 23:34:15 2022 +0200 Move the `Unclosed` inner class to a top-level class, renamed `Fragments`. There is no code change (other than move/renaming) in this commit. --- .../sis/internal/processing/isoline/Fragments.java | 270 ++++++++++++++++++++ .../processing/isoline/PolylineBuffer.java | 2 +- .../sis/internal/processing/isoline/Tracer.java | 272 ++------------------- 3 files changed, 285 insertions(+), 259 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 new file mode 100644 index 0000000000..da97430340 --- /dev/null +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Fragments.java @@ -0,0 +1,270 @@ +/* + * 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.awt.Point; +import java.util.Map; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import org.apache.sis.internal.util.Numerics; + + +/** + * 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 Tracer.Level}. + * Those copies are performed for saving data before they are overwritten by next iterated cell. + * + * <h2>List indices and ordering of points</h2> + * For a given {@code Fragments} list, all {@code double[]} arrays at even indices shall have their points read + * in reverse order and all {@code double[]} arrays at odd indices shall have their points read in forward order. + * The list size must be even and the list may contain null elements when there is no data in the corresponding + * iteration order. This convention makes easy to reverse the order of all points, simply by reversing the order + * 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 + * @module + */ +@SuppressWarnings({"CloneableImplementsClone", "serial"}) // Not intended to be cloned or serialized. +final class Fragments extends ArrayList<double[]> { + /** + * The first points and last point in this list of polylines. By convention the coordinate having fraction + * digits has all its bits inverted by the {@code ~} operator. May be {@code null} if a coordinate is NaN. + * Do not modify {@link Point} field values, because those instances are keys in {@link Tracer.Level#partialPaths}. + */ + private Point firstPoint, lastPoint; + + /** + * Creates a list of polylines initialized to the given items. + * The given polylines and their opposite directions are cleared by this method. + * + * @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. + */ + Fragments(final PolylineBuffer polylineOnLeft, final PolylineBuffer polylineOnTop) { + /* + * 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: + * + * ✓ (index & 2) = 0 if using `polylineOnLeft` (otherwise `polylineOnTop`). + * ✓ (index % 3) = 0 if using `opposite` value of polyline (may be null). + * ✓ (index & 1) = 0 if fetching last point (otherwise fetch first point). + * + * Index PolylineBuffer (order) !(i & 2) !(i % 3) !(i & 1) Comment + * ──────────────────────────────────────────────────────────────────────────── + * [0] polylineOnLeft.opposite (←) ✓ ✓ ✓ (1) + * [1] polylineOnLeft (→) ✓ (2) + * [2] polylineOnTop (←) ✓ (1) + * [3] polylineOnTop.opposite (→) ✓ (2) + * [4] ✓ ✓ + * |5] ✓ + * [6] polylineOnTop.opposite (→) ✓ ✓ (3) + * [7] polylineOnTop (←) (4) + * [8] polylineOnLeft (→) ✓ ✓ (3) + * [9] polylineOnLeft.opposite (←) ✓ ✓ (4) + * + * Comments: + * (1) Last `PolylineBuffer` point is first `Fragments` point because of reverse iteration order. + * (2) First `PolylineBuffer` point is first `Fragments` point because of forward iteration order. + * (3) Last `PolylineBuffer` point is last `Fragments` point because of forward iteration order. + * (4) First `PolylineBuffer` point is last `Fragments` point because of reverse iteration order. + */ + int index = 0; + do { + 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) { + final double[] coordinates = polyline.coordinates; + final double x, y; + if (((index & 1) == 0)) { // See above table in comment (column 6). + y = coordinates[--n]; + x = coordinates[--n]; + } else { + x = coordinates[0]; + y = coordinates[1]; + } + final boolean isLastPoint = (index >= 6); // See row [6] in above table. + if (Double.isFinite(x) && Double.isFinite(y)) { + final Point p = new Point((int) x, (int) y); + if (!Numerics.isInteger(x)) p.x = ~p.x; + if (!Numerics.isInteger(y)) p.y = ~p.y; + if (isLastPoint) { + lastPoint = p; + break; // Done searching both points. + } + firstPoint = p; + } else if (isLastPoint) { + /* + * If the last point was NaN, check if it was also the case of first point. + * If yes, we will not be able to store this `Fragments` in `partialPaths` + * because we have no point that we can use as key (it would be pointless + * to search for another point further in the `coordinates` array because + * that point could never be matched with another `Fragments`). Leave this + * list empty for avoiding the copies done by `take(…)` calls. Instead, + * callers should write polylines in `Tracer.Level.path` immediately. + */ + if (firstPoint == null) return; + break; + } + /* + * Done searching the first point (may still be null if that point is NaN). + * Row [6] in above table is the first row for the search of last point. + */ + index = 6; + continue; + } + } + if (++index == 4) { + // Found no non-empty polylines during search for first point. No need to continue searching. + return; + } + } while (index <= 9); + /* + * Copies coordinates only if at least one of `firstPoint` or `lastPoint` is a valid point. + */ + take(polylineOnLeft.opposite); // Point will be iterated in reverse order. + take(polylineOnLeft); // Point will be iterated in forward order. + if (polylineOnTop != null) { + PolylineBuffer suffix = polylineOnTop.opposite; + take(polylineOnTop); // Inverse order. Set `polylineOnTop.opposite` to null. + take(suffix); // Forward order. + } + } + + /** + * Takes a copy of coordinate values of given polyline, then clears that polyline. + */ + private void take(final PolylineBuffer polyline) { + if (polyline != null && polyline.size != 0) { + add(Arrays.copyOf(polyline.coordinates, polyline.size)); + polyline.clear(); + } else { + add(null); // No data for iteration order at this position. + } + } + + /** + * Returns {@code true} if the given point is equal to the start point or end point. + * This is used in assertions for checking key validity in {@link Tracer.Level#partialPaths}. + */ + final boolean isExtremity(final Point key) { + return key.equals(firstPoint) || key.equals(lastPoint); + } + + /** + * Associates this polyline to its two extremities in the given map. If other polylines already exist + * for one or both extremities, then this polyline will be merged with previously existing polylines. + * This method returns {@code true} if the polyline has been closed, in which case caller should store + * the coordinates in {@link Tracer.Level#path} immediately. + * + * @param partialPaths where to add or merge polylines. + * @return {@code true} if this polyline became a closed polygon as a result of merge operation. + */ + final boolean addOrMerge(final Map<Point,Fragments> partialPaths) { + final Fragments before = partialPaths.remove(firstPoint); + final Fragments after = partialPaths.remove(lastPoint); + if (before != null) partialPaths.remove(addAll(before, true)); + if (after != null) partialPaths.remove(addAll(after, false)); + if (firstPoint != null && firstPoint.equals(lastPoint)) { // First/last points may have changed. + partialPaths.remove(firstPoint); + partialPaths.remove(lastPoint); + return true; + } else { + // Intentionally replace previous values. + if (firstPoint != null) partialPaths.put(firstPoint, this); + if (lastPoint != null) partialPaths.put(lastPoint, this); + return false; + } + } + + /** + * Prepends or appends the given polylines to this list of polylines. + * Points order will be changed as needed in order to match extremities. + * The {@code other} instance should be forgotten after this method call. + * + * @param other the other polyline to append or prepend to this polyline. + * @param prepend {@code true} for prepend operation, {@code false} for append. + * @return extremity of {@code other} which has not been assigned to {@code this}. + */ + private Point addAll(final Fragments other, final boolean prepend) { + assert ((size() | other.size()) & 1) == 0; // Must have even number of elements in both lists. + /* + * In figures below, ● are the extremities to attach together. + * `r` is a bitmask telling which polylines to reverse: + * 1=this, 2=other, together with combinations 0=none and 3=other. + */ + int r; if ( lastPoint != null && lastPoint.equals(other.firstPoint)) r = 0; // ○──────● ●──────○ + else if (firstPoint != null && firstPoint.equals(other.firstPoint)) r = 1; // ●──────○ ●──────○ + else if ( lastPoint != null && lastPoint.equals(other. lastPoint)) r = 2; // ○──────● ○──────● + else if (firstPoint != null && firstPoint.equals(other. lastPoint)) r = 3; // ●──────○ ○──────● + else { + // Should never happen because `other` has been obtained using a point of `this`. + throw new AssertionError(); + } + if (prepend) r ^= 3; // Swap order in above ○──○ ○──○ figures. + if ((r & 1) != 0) this.reverse(); + if ((r & 2) != 0) other.reverse(); + if (prepend) { + addAll(0, other); + firstPoint = other.firstPoint; + return other.lastPoint; + } else { + addAll(other); + lastPoint = other.lastPoint; + return other.firstPoint; + } + } + + /** + * Reverse the order of all points. The last polyline will become the first polyline and vice-versa. + * For each polyline, points will be iterated in opposite order. The trick on point order is done by + * moving polylines at even indices to odd indices, and conversely (see class javadoc for convention + * about even/odd indices). + */ + private void reverse() { + Collections.reverse(this); + final Point swap = firstPoint; + firstPoint = lastPoint; + lastPoint = swap; + } + + /** + * 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, PolylineBuffer[], boolean) + */ + 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 PolylineBuffer(coordinates); + } + } + return polylines; + } +} 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 index 384c765a17..5da2768203 100644 --- 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 @@ -70,7 +70,7 @@ final class PolylineBuffer { } /** - * Creates a new polyline wrapping the given coordinates. Used for wrapping {@link Unclosed} + * Creates a new polyline wrapping the given coordinates. Used for wrapping {@link Fragments} * instances in objects expected by {@link Tracer#writeTo(Joiner, Polyline[], boolean)}. * Those {@code Polyline} instances are temporary. */ 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 c7b4bb564e..17c777e460 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 @@ -16,12 +16,9 @@ */ package org.apache.sis.internal.processing.isoline; -import java.util.Arrays; -import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.IdentityHashMap; -import java.util.Collections; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; @@ -29,7 +26,6 @@ import java.awt.geom.Path2D; 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.Debug; @@ -215,14 +211,14 @@ final class Tracer { * by the {@code ~} operator. For each point, there is at most one coordinate having such fraction digits. * * <h4>Map values</h4> - * {@code Unclosed} instances are list of {@code double[]} arrays to be concatenated in a single polygon later. - * For a given {@code Unclosed} list, all {@code double[]} arrays at even indices shall have their points read + * {@code Fragments} instances are list of {@code double[]} arrays to be concatenated in a single polygon later. + * For a given {@code Fragments} list, all {@code double[]} arrays at even indices shall have their points read * 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(PolylineBuffer) */ - private final Map<Point,Unclosed> partialPaths; + private final Map<Point,Fragments> partialPaths; /** * Builder of isolines as a Java2D shape, created when first needed. @@ -539,7 +535,7 @@ final class Tracer { * Joining left and top polylines do not yet create a closed shape. Consequently we may not write * in the `path` now. But maybe we can close the polygon later after more polylines are attached. */ - final Unclosed fragment = new Unclosed(polylineOnLeft, polylineOnTop); + final Fragments fragment = new Fragments(polylineOnLeft, polylineOnTop); if (fragment.isEmpty()) { /* * Fragment starts and ends with NaN values. We will not be able to complete a polygon. @@ -566,8 +562,8 @@ 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 PolylineBuffer polyline) throws TransformException { - final Unclosed fragment = new Unclosed(polyline, null); + private void writeFragment(final PolylineBuffer polyline) throws TransformException { + final Fragments fragment = new Fragments(polyline, null); final PolylineBuffer[] polylines; final boolean close; if (fragment.isEmpty()) { @@ -591,7 +587,7 @@ final class Tracer { */ final void finishedRow() throws TransformException { if (!polylineOnLeft.transferToOpposite()) { - writeUnclosed(polylineOnLeft); + writeFragment(polylineOnLeft); } isDataAbove = 0; } @@ -611,7 +607,7 @@ final class Tracer { * reading the `shape` field. */ for (int i=0; i < polylinesOnTop.length; i++) { - writeUnclosed(polylinesOnTop[i]); + writeFragment(polylinesOnTop[i]); polylinesOnTop[i] = null; } assert isConsistent(); @@ -621,7 +617,7 @@ final class Tracer { * Verifies that {@link #partialPaths} consistency. Used for assertions only. */ private boolean isConsistent() { - for (final Map.Entry<Point,Unclosed> entry : partialPaths.entrySet()) { + for (final Map.Entry<Point,Fragments> entry : partialPaths.entrySet()) { if (!entry.getValue().isExtremity(entry.getKey())) return false; } return true; @@ -645,9 +641,9 @@ final class Tracer { other.path = null; assert this.isConsistent(); assert other.isConsistent(); - final IdentityHashMap<Unclosed,Boolean> done = new IdentityHashMap<>(other.partialPaths.size() / 2); - for (final Map.Entry<Point,Unclosed> entry : other.partialPaths.entrySet()) { - final Unclosed fragment = entry.getValue(); + final IdentityHashMap<Fragments,Boolean> done = new IdentityHashMap<>(other.partialPaths.size() / 2); + for (final Map.Entry<Point,Fragments> entry : other.partialPaths.entrySet()) { + final Fragments fragment = entry.getValue(); if (done.put(fragment, Boolean.TRUE) == null) { assert fragment.isExtremity(entry.getKey()); if (fragment.addOrMerge(partialPaths)) { @@ -667,8 +663,8 @@ final class Tracer { * @throws TransformException if an error occurred during polylines creation. */ final void flush() throws TransformException { - for (final Map.Entry<Point,Unclosed> entry : partialPaths.entrySet()) { - final Unclosed fragment = entry.getValue(); + for (final Map.Entry<Point,Fragments> entry : partialPaths.entrySet()) { + final Fragments fragment = entry.getValue(); assert fragment.isExtremity(entry.getKey()); if (!fragment.isEmpty()) { path = writeTo(path, fragment.toPolylines(), false); @@ -702,246 +698,6 @@ final class Tracer { } } - /** - * 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> - * For a given {@code Unclosed} list, all {@code double[]} arrays at even indices shall have their points read - * in reverse order and all {@code double[]} arrays at odd indices shall have their points read in forward order. - * The list size must be even and the list may contain null elements when there is no data in the corresponding - * iteration order. This convention makes easy to reverse the order of all points, simply by reversing the order - * 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 Level#partialPaths - */ - @SuppressWarnings({"CloneableImplementsClone", "serial"}) // Not intended to be cloned or serialized. - private static final class Unclosed extends ArrayList<double[]> { - /** - * The first points and last point in this list of polylines. By convention the coordinate having fraction - * digits has all its bits inverted by the {@code ~} operator. May be {@code null} if a coordinate is NaN. - * Do not modify {@link Point} field values, because those instances are keys in {@link Level#partialPaths}. - */ - private Point firstPoint, lastPoint; - - /** - * Creates a list of polylines initialized to the given items. - * The given polylines and their opposite directions are cleared by this method. - * - * @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 PolylineBuffer polylineOnLeft, final PolylineBuffer polylineOnTop) { - /* - * 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: - * - * ✓ (index & 2) = 0 if using `polylineOnLeft` (otherwise `polylineOnTop`). - * ✓ (index % 3) = 0 if using `opposite` value of polyline (may be null). - * ✓ (index & 1) = 0 if fetching last point (otherwise fetch first point). - * - * Index PolylineBuffer (order) !(i & 2) !(i % 3) !(i & 1) Comment - * ──────────────────────────────────────────────────────────────────────────── - * [0] polylineOnLeft.opposite (←) ✓ ✓ ✓ (1) - * [1] polylineOnLeft (→) ✓ (2) - * [2] polylineOnTop (←) ✓ (1) - * [3] polylineOnTop.opposite (→) ✓ (2) - * [4] ✓ ✓ - * |5] ✓ - * [6] polylineOnTop.opposite (→) ✓ ✓ (3) - * [7] polylineOnTop (←) (4) - * [8] polylineOnLeft (→) ✓ ✓ (3) - * [9] polylineOnLeft.opposite (←) ✓ ✓ (4) - * - * Comments: - * (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 { - 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) { - final double[] coordinates = polyline.coordinates; - final double x, y; - if (((index & 1) == 0)) { // See above table in comment (column 6). - y = coordinates[--n]; - x = coordinates[--n]; - } else { - x = coordinates[0]; - y = coordinates[1]; - } - final boolean isLastPoint = (index >= 6); // See row [6] in above table. - if (Double.isFinite(x) && Double.isFinite(y)) { - final Point p = new Point((int) x, (int) y); - if (!Numerics.isInteger(x)) p.x = ~p.x; - if (!Numerics.isInteger(y)) p.y = ~p.y; - if (isLastPoint) { - lastPoint = p; - break; // Done searching both points. - } - firstPoint = p; - } else if (isLastPoint) { - /* - * If the last point was NaN, check if it was also the case of first point. - * If yes, we will not be able to store this `Unclosed` in `partialPaths` - * because we have no point that we can use as key (it would be pointless - * to search for another point further in the `coordinates` array because - * that point could never be matched with another `Unclosed`). Leave this - * list empty for avoiding the copies done by `take(…)` calls. Instead, - * callers should write polylines in `Level.path` immediately. - */ - if (firstPoint == null) return; - break; - } - /* - * Done searching the first point (may still be null if that point is NaN). - * Row [6] in above table is the first row for the search of last point. - */ - index = 6; - continue; - } - } - if (++index == 4) { - // Found no non-empty polylines during search for first point. No need to continue searching. - return; - } - } while (index <= 9); - /* - * Copies coordinates only if at least one of `firstPoint` or `lastPoint` is a valid point. - */ - take(polylineOnLeft.opposite); // Point will be iterated in reverse order. - take(polylineOnLeft); // Point will be iterated in forward order. - if (polylineOnTop != null) { - PolylineBuffer suffix = polylineOnTop.opposite; - take(polylineOnTop); // Inverse order. Set `polylineOnTop.opposite` to null. - take(suffix); // Forward order. - } - } - - /** - * Takes a copy of coordinate values of given polyline, then clears that polyline. - */ - private void take(final PolylineBuffer polyline) { - if (polyline != null && polyline.size != 0) { - add(Arrays.copyOf(polyline.coordinates, polyline.size)); - polyline.clear(); - } else { - add(null); // No data for iteration order at this position. - } - } - - /** - * Returns {@code true} if the given point is equal to the start point or end point. - * This is used in assertions for checking key validity in {@link Level#partialPaths}. - */ - final boolean isExtremity(final Point key) { - return key.equals(firstPoint) || key.equals(lastPoint); - } - - /** - * Associates this polyline to its two extremities in the given map. If other polylines already exist - * for one or both extremities, then this polyline will be merged with previously existing polylines. - * This method returns {@code true} if the polyline has been closed, in which case caller should store - * the coordinates in {@link Level#path} immediately. - * - * @param partialPaths where to add or merge polylines. - * @return {@code true} if this polyline became a closed polygon as a result of merge operation. - */ - final boolean addOrMerge(final Map<Point,Unclosed> partialPaths) { - final Unclosed before = partialPaths.remove(firstPoint); - final Unclosed after = partialPaths.remove(lastPoint); - if (before != null) partialPaths.remove(addAll(before, true)); - if (after != null) partialPaths.remove(addAll(after, false)); - if (firstPoint != null && firstPoint.equals(lastPoint)) { // First/last points may have changed. - partialPaths.remove(firstPoint); - partialPaths.remove(lastPoint); - return true; - } else { - // Intentionally replace previous values. - if (firstPoint != null) partialPaths.put(firstPoint, this); - if (lastPoint != null) partialPaths.put(lastPoint, this); - return false; - } - } - - /** - * Prepends or appends the given polylines to this list of polylines. - * Points order will be changed as needed in order to match extremities. - * The {@code other} instance should be forgotten after this method call. - * - * @param other the other polyline to append or prepend to this polyline. - * @param prepend {@code true} for prepend operation, {@code false} for append. - * @return extremity of {@code other} which has not been assigned to {@code this}. - */ - private Point addAll(final Unclosed other, final boolean prepend) { - assert ((size() | other.size()) & 1) == 0; // Must have even number of elements in both lists. - /* - * In figures below, ● are the extremities to attach together. - * `r` is a bitmask telling which polylines to reverse: - * 1=this, 2=other, together with combinations 0=none and 3=other. - */ - int r; if ( lastPoint != null && lastPoint.equals(other.firstPoint)) r = 0; // ○──────● ●──────○ - else if (firstPoint != null && firstPoint.equals(other.firstPoint)) r = 1; // ●──────○ ●──────○ - else if ( lastPoint != null && lastPoint.equals(other. lastPoint)) r = 2; // ○──────● ○──────● - else if (firstPoint != null && firstPoint.equals(other. lastPoint)) r = 3; // ●──────○ ○──────● - else { - // Should never happen because `other` has been obtained using a point of `this`. - throw new AssertionError(); - } - if (prepend) r ^= 3; // Swap order in above ○──○ ○──○ figures. - if ((r & 1) != 0) this.reverse(); - if ((r & 2) != 0) other.reverse(); - if (prepend) { - addAll(0, other); - firstPoint = other.firstPoint; - return other.lastPoint; - } else { - addAll(other); - lastPoint = other.lastPoint; - return other.firstPoint; - } - } - - /** - * Reverse the order of all points. The last polyline will become the first polyline and vice-versa. - * For each polyline, points will be iterated in opposite order. The trick on point order is done by - * moving polylines at even indices to odd indices, and conversely (see class javadoc for convention - * about even/odd indices). - */ - private void reverse() { - Collections.reverse(this); - final Point swap = firstPoint; - firstPoint = lastPoint; - lastPoint = swap; - } - - /** - * 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, PolylineBuffer[], boolean) - */ - 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 PolylineBuffer(coordinates); - } - } - return polylines; - } - } - /** * 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,