| /* |
| * 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. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.3 |
| * |
| * @see Tracer.Level#partialPaths |
| * |
| * @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; |
| } |
| } |