001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.euclidean.internal;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.NavigableSet;
022import java.util.TreeSet;
023
024/** Abstract base class for joining unconnected path elements into connected, directional
025 * paths. The connection algorithm is exposed as a set of protected methods, allowing subclasses
026 * to define their own public API. Implementations must supply their own subclass of {@link ConnectableElement}
027 * specific for the objects being connected.
028 *
029 * <p>The connection algorithm proceeds as follows:
030 * <ul>
031 *      <li>Create a sorted list of {@link ConnectableElement}s.</li>
032 *      <li>For each element, attempt to find other elements with start points next the
033 *      first instance's end point by calling {@link ConnectableElement#getConnectionSearchKey()} and
034 *      using the returned instance to locate a search start location in the sorted element list.</li>
035 *      <li>Search up through the sorted list from the start location, testing each element for possible connectivity
036 *      with {@link ConnectableElement#canConnectTo(AbstractPathConnector.ConnectableElement)}. Collect possible
037 *      connections in a list. Terminate the search when
038 *      {@link ConnectableElement#shouldContinueConnectionSearch(AbstractPathConnector.ConnectableElement, boolean)}
039 *      returns false.
040 *      <li>Repeat the previous step searching downward through the list from the start location.</li>
041 *      <li>Select the best connection option from the list of possible connections, using
042 *      {@link #selectPointConnection(AbstractPathConnector.ConnectableElement, List)}
043 *      and/or {@link #selectConnection(AbstractPathConnector.ConnectableElement, List)} when multiple possibilities
044 *      are found.</li>
045 *      <li>Repeat the above steps for each element. When done, the elements represent a linked list
046 *      of connected paths.</li>
047 * </ul>
048 *
049 * <p>This class is not thread-safe.</p>
050 *
051 * @param <E> Element type
052 * @see ConnectableElement
053 */
054public abstract class AbstractPathConnector<E extends AbstractPathConnector.ConnectableElement<E>> {
055    /** List of path elements. */
056    private final NavigableSet<E> pathElements = new TreeSet<>();
057
058    /** View of the path element set in descending order. */
059    private final NavigableSet<E> pathElementsDescending = pathElements.descendingSet();
060
061    /** List used to store possible connections for the current element. */
062    private final List<E> possibleConnections = new ArrayList<>();
063
064    /** List used to store possible point-like (zero-length) connections for the current element. */
065    private final List<E> possiblePointConnections = new ArrayList<>();
066
067    /** Add a collection of path elements to the connector and attempt to connect each new element
068     * with previously added ones.
069     * @param elements path elements to connect
070     */
071    protected void connectPathElements(final Iterable<E> elements) {
072        elements.forEach(this::addPathElement);
073
074        for (final E element : elements) {
075            makeForwardConnection(element);
076        }
077    }
078
079    /** Add a single path element to the connector, leaving it unconnected until a later call to
080     * to {@link #connectPathElements(Iterable)} or {@link #computePathRoots()}.
081     * @param element value to add to the connector
082     * @see #connectPathElements(Iterable)
083     * @see #computePathRoots()
084     */
085    protected void addPathElement(final E element) {
086        pathElements.add(element);
087    }
088
089    /** Compute all connected paths and return a list of path elements representing
090     * the roots (start locations) of each. Each returned element is the head of a
091     * (possibly circular) linked list that follows a connected path.
092     *
093     * <p>The connector is reset after this call. Further calls to add elements
094     * will result in new paths being generated.</p>
095     * @return a list of root elements for the computed connected paths
096     */
097    protected List<E> computePathRoots() {
098        for (final E element : pathElements) {
099            followForwardConnections(element);
100        }
101
102        final List<E> rootEntries = new ArrayList<>();
103        E root;
104        for (final E element : pathElements) {
105            root = element.exportPath();
106            if (root != null) {
107                rootEntries.add(root);
108            }
109        }
110
111        pathElements.clear();
112        possibleConnections.clear();
113        possiblePointConnections.clear();
114
115        return rootEntries;
116    }
117
118    /** Find and follow forward connections from the given start element.
119     * @param start element to begin the connection operation with
120     */
121    private void followForwardConnections(final E start) {
122        E current = start;
123
124        while (current != null && current.hasEnd() && !current.hasNext()) {
125            current = makeForwardConnection(current);
126        }
127    }
128
129    /** Connect the end point of the given element to the start point of another element. Returns
130     * the newly connected element or null if no forward connection was made.
131     * @param element element to connect
132     * @return the next element in the path or null if no connection was made
133     */
134    private E makeForwardConnection(final E element) {
135        findPossibleConnections(element);
136
137        E next = null;
138
139        // select from all available connections, handling point-like segments first
140        if (!possiblePointConnections.isEmpty()) {
141            next = (possiblePointConnections.size() == 1) ?
142                    possiblePointConnections.get(0) :
143                    selectPointConnection(element, possiblePointConnections);
144        } else if (!possibleConnections.isEmpty()) {
145
146            next = (possibleConnections.size() == 1) ?
147                    possibleConnections.get(0) :
148                    selectConnection(element, possibleConnections);
149        }
150
151        if (next != null) {
152            element.connectTo(next);
153        }
154
155        return next;
156    }
157
158    /** Find possible connections for the given element and place them in the
159     * {@link #possibleConnections} and {@link #possiblePointConnections} lists.
160     * @param element the element to find connections for
161     */
162    private void findPossibleConnections(final E element) {
163        possibleConnections.clear();
164        possiblePointConnections.clear();
165
166        if (element.hasEnd()) {
167            final E searchKey = element.getConnectionSearchKey();
168
169            // search up
170            for (final E candidate : pathElements.tailSet(searchKey)) {
171                if (!addPossibleConnection(element, candidate) &&
172                        !element.shouldContinueConnectionSearch(candidate, true)) {
173                    break;
174                }
175            }
176
177            // search down
178            for (final E candidate : pathElementsDescending.tailSet(searchKey, false)) {
179                if (!addPossibleConnection(element, candidate) &&
180                        !element.shouldContinueConnectionSearch(candidate, false)) {
181                    break;
182                }
183            }
184        }
185    }
186
187    /** Add the candidate to one of the connection lists if it represents a possible connection. Returns
188     * true if the candidate was added, otherwise false.
189     * @param element element to check for connections with
190     * @param candidate candidate connection element
191     * @return true if the candidate is a possible connection
192     */
193    private boolean addPossibleConnection(final E element, final E candidate) {
194        if (element != candidate &&
195                !candidate.hasPrevious() &&
196                candidate.hasStart() &&
197                element.canConnectTo(candidate)) {
198
199            if (element.endPointsEq(candidate)) {
200                possiblePointConnections.add(candidate);
201            } else {
202                possibleConnections.add(candidate);
203            }
204
205            return true;
206        }
207
208        return false;
209    }
210
211    /** Method called to select a connection to use for a given element when multiple zero-length connections are
212     * available. The algorithm here attempts to choose the point most likely to produce a logical path by selecting
213     * the outgoing element with the smallest relative angle with the incoming element, with unconnected element
214     * preferred over ones that are already connected (thereby allowing other connections to occur in the path).
215     * @param incoming the incoming element
216     * @param outgoingList list of available outgoing point-like connections
217     * @return the connection to use
218     */
219    protected E selectPointConnection(final E incoming, final List<E> outgoingList) {
220
221        double angle;
222        boolean isUnconnected;
223
224        double smallestAngle = 0.0;
225        E bestElement = null;
226        boolean bestIsUnconnected = false;
227
228        for (final E outgoing : outgoingList) {
229            angle = Math.abs(incoming.getRelativeAngle(outgoing));
230            isUnconnected = !outgoing.hasNext();
231
232            if (bestElement == null || (!bestIsUnconnected && isUnconnected) ||
233                    (bestIsUnconnected == isUnconnected && angle < smallestAngle)) {
234
235                smallestAngle = angle;
236                bestElement = outgoing;
237                bestIsUnconnected = isUnconnected;
238            }
239        }
240
241        return bestElement;
242    }
243
244    /** Method called to select a connection to use for a given segment when multiple non-length-zero
245     * connections are available. In this case, the selection of the outgoing connection depends only
246     * on the desired characteristics of the connected path.
247     * @param incoming the incoming segment
248     * @param outgoing list of available outgoing connections; will always contain at least
249     *      two elements
250     * @return the connection to use
251     */
252    protected abstract E selectConnection(E incoming, List<E> outgoing);
253
254    /** Class used to represent connectable path elements for use with {@link AbstractPathConnector}.
255     * Subclasses must fulfill the following requirements in order for path connection operations
256     * to work correctly:
257     * <ul>
258     *      <li>Implement {@link #compareTo(Object)} such that elements are sorted by their start
259     *      point locations. Other criteria may be used as well but elements with start points in close
260     *      proximity must be grouped together.</li>
261     *      <li>Implement {@link #getConnectionSearchKey()} such that it returns an instance that will be placed
262     *      next to elements with start points close to the current instance's end point when sorted with
263     *      {@link #compareTo(Object)}.</li>
264     *      <li>Implement {@link #shouldContinueConnectionSearch(AbstractPathConnector.ConnectableElement, boolean)}
265     *      such that it returns false when the search for possible connections through a sorted list of elements
266     *      may terminate.</li>
267     * </ul>
268     *
269     * @param <E> Element type
270     * @see AbstractPathConnector
271     */
272    public abstract static class ConnectableElement<E extends ConnectableElement<E>>
273        implements Comparable<E> {
274        /** Next connected element. */
275        private E next;
276
277        /** Previous connected element. */
278        private E previous;
279
280        /** Flag set to true when this element has exported its value to a path. */
281        private boolean exported;
282
283        /** Return true if the instance is connected to another element's start point.
284         * @return true if the instance has a next element
285         */
286        public boolean hasNext() {
287            return next != null;
288        }
289
290        /** Get the next connected element in the path, if any.
291         * @return the next connected segment in the path; may be null
292         */
293        public E getNext() {
294            return next;
295        }
296
297        /** Set the next connected element for this path. This is intended for
298         * internal use only. Callers should use the {@link #connectTo(AbstractPathConnector.ConnectableElement)}
299         * method instead.
300         * @param next next path element
301         */
302        protected void setNext(final E next) {
303            this.next = next;
304        }
305
306        /** Return true if another element is connected to this instance's start point.
307         * @return true if the instance has a previous element
308         */
309        public boolean hasPrevious() {
310            return previous != null;
311        }
312
313        /** Get the previous connected element in the path, if any.
314         * @return the previous connected element in the path; may be null
315         */
316        public E getPrevious() {
317            return previous;
318        }
319
320        /** Set the previous connected element for this path. This is intended for
321         * internal use only. Callers should use the {@link #connectTo(AbstractPathConnector.ConnectableElement)}
322         * method instead.
323         * @param previous previous path element
324         */
325        protected void setPrevious(final E previous) {
326            this.previous = previous;
327        }
328
329        /** Connect this instance's end point to the given element's start point. No validation
330         * is performed in this method. The {@link #canConnectTo(AbstractPathConnector.ConnectableElement)}
331         * method must have been called previously.
332         * @param nextElement the next element in the path
333         */
334        public void connectTo(final E nextElement) {
335            setNext(nextElement);
336            nextElement.setPrevious(getSelf());
337        }
338
339        /** Export the path that this element belongs to, returning the root
340         * segment. This method traverses all connected element, sets their
341         * exported flags to true, and returns the root element of the path
342         * (or this element in the case of a loop). Each path can only be
343         * exported once. Later calls to this method on this instance or any of its
344         * connected elements will return null.
345         * @return the root of the path or null if the path that this element
346         *      belongs to has already been exported
347         */
348        public E exportPath() {
349            if (markExported()) {
350
351                // export the connected portions of the path, moving both
352                // forward and backward
353                E current;
354                E root = getSelf();
355
356                // forward
357                current = next;
358                while (current != null && current.markExported()) {
359                    current = current.getNext();
360                }
361
362                // backward
363                current = previous;
364                while (current != null && current.markExported()) {
365                    root = current;
366                    current = current.getPrevious();
367                }
368
369                return root;
370            }
371
372            return null;
373        }
374
375        /** Set the export flag for this instance to true. Returns true
376         * if the flag was changed and false otherwise.
377         * @return true if the flag was changed and false if it was
378         *      already set to true
379         */
380        protected boolean markExported() {
381            if (!exported) {
382                exported = true;
383                return true;
384            }
385            return false;
386        }
387
388        /** Return true if this instance has a start point that can be
389         * connected to another element's end point.
390         * @return true if this instance has a start point that can be
391         *      connected to another element's end point
392         */
393        public abstract boolean hasStart();
394
395        /** Return true if this instance has an end point that can be
396         * connected to another element's start point.
397         * @return true if this instance has an end point that can be
398         *      connected to another element's start point
399         */
400        public abstract boolean hasEnd();
401
402        /** Return true if the end point of this instance should be considered
403         * equivalent to the end point of the argument.
404         * @param other element to compare end points with
405         * @return true if this instance has an end point equivalent to that
406         *      of the argument
407         */
408        public abstract boolean endPointsEq(E other);
409
410        /** Return true if this instance's end point can be connected to
411         * the argument's start point.
412         * @param nextElement candidate for the next element in the path; this value
413         *      is guaranteed to not be null and to contain a start point
414         * @return true if this instance's end point can be connected to
415         *      the argument's start point
416         */
417        public abstract boolean canConnectTo(E nextElement);
418
419        /** Return the relative angle between this element and the argument.
420         * @param other element to compute the angle with
421         * @return the relative angle between this element and the argument
422         */
423        public abstract double getRelativeAngle(E other);
424
425        /** Get a new instance used as a search key to help locate other elements
426         * with start points matching this instance's end point. The only restriction
427         * on the returned instance is that it be compatible with the implementation
428         * class' {@link #compareTo(Object)} method.
429         * @return a new instance used to help locate other path elements with start
430         *      points equivalent to this instance's end point
431         */
432        public abstract E getConnectionSearchKey();
433
434        /** Return true if the search for possible connections should continue through
435         * the sorted set of possible path elements given the current candidate element
436         * and search direction. The search operation stops for the given direction
437         * when this method returns false.
438         * @param candidate last tested candidate connection element
439         * @param ascending true if the search is proceeding in an ascending direction;
440         *      false otherwise
441         * @return true if the connection search should continue
442         */
443        public abstract boolean shouldContinueConnectionSearch(E candidate, boolean ascending);
444
445        /** Return the current instance as the generic type.
446         * @return the current instance as the generic type.
447         */
448        protected abstract E getSelf();
449    }
450}