Move the `Unclosed` inner class to a top-level class, renamed `Fragments`.
There is no code change (other than move/renaming) in this commit.
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 0000000..da97430
--- /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 384c765..5da2768 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 @@
}
/**
- * 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 c7b4bb5..17c777e 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 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 @@
* 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 @@
* 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 @@
* 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 void finishedRow() throws TransformException {
if (!polylineOnLeft.transferToOpposite()) {
- writeUnclosed(polylineOnLeft);
+ writeFragment(polylineOnLeft);
}
isDataAbove = 0;
}
@@ -611,7 +607,7 @@
* 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 @@
* 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 @@
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 @@
* @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);
@@ -703,246 +699,6 @@
}
/**
- * 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,
* then apply a {@link MathTransform} on all coordinate values.