View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.euclidean.twod.path;
18  
19  import java.text.MessageFormat;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.stream.Collectors;
26  import java.util.stream.Stream;
27  
28  import org.apache.commons.geometry.core.Sized;
29  import org.apache.commons.geometry.core.Transform;
30  import org.apache.commons.geometry.euclidean.twod.BoundarySource2D;
31  import org.apache.commons.geometry.euclidean.twod.Line;
32  import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
33  import org.apache.commons.geometry.euclidean.twod.Lines;
34  import org.apache.commons.geometry.euclidean.twod.Vector2D;
35  import org.apache.commons.numbers.core.Precision;
36  
37  /** Class representing a connected path of {@link LineConvexSubset line convex subsets}.
38   * The elements in the path are connected end to end, with the end vertex of the previous
39   * element equivalent to the start vertex of the next element. Elements are not required to
40   * be finite. However, since path elements are connected, only the first element and/or last
41   * element may be infinite.
42   *
43   * <p>Instances of this class are guaranteed to be immutable.</p>
44   */
45  public class LinePath implements BoundarySource2D, Sized {
46      /** Line path instance containing no elements. */
47      private static final LinePath EMPTY = new LinePath(Collections.emptyList());
48  
49      /** The line convex subsets comprising the path. */
50      private final List<LineConvexSubset> elements;
51  
52      /** Simple constructor. Callers are responsible for ensuring that the given list of
53       * line subsets defines a valid path. No validation is performed.
54       * @param elements elements defining the path.
55       */
56      LinePath(final List<LineConvexSubset> elements) {
57          this.elements = Collections.unmodifiableList(elements);
58      }
59  
60      /** {@inheritDoc} */
61      @Override
62      public Stream<LineConvexSubset> boundaryStream() {
63          return getElements().stream();
64      }
65  
66      /** Get the sequence of line subsets comprising the path.
67       * @return the sequence of line subsets comprising the path
68       */
69      public List<LineConvexSubset> getElements() {
70          return elements;
71      }
72  
73      /** Get the line subset at the start of the path or null if the path is empty. If the
74       * path consists of a single line subset, then the returned instance with be the same
75       * as that returned by {@link #getEnd()}.
76       * @return the line subset at the start of the path or null if the path is empty
77       * @see #getEnd()
78       */
79      public LineConvexSubset getStart() {
80          if (!isEmpty()) {
81              return elements.get(0);
82          }
83          return null;
84      }
85  
86      /** Get the line subset at the end of the path or null if the path is empty. If the
87       * path consists of a single line subset, then the returned instance with be the same
88       * as that returned by {@link #getStart()}.
89       * @return the line subset at the end of the path or null if the path is empty
90       * @see #getStart()
91       */
92      public LineConvexSubset getEnd() {
93          if (!isEmpty()) {
94              return elements.get(elements.size() - 1);
95          }
96          return null;
97      }
98  
99      /** Get the sequence of vertices defined by the path. Vertices appear in the
100      * list as many times as they are visited in the path. For example, the vertex
101      * sequence for a closed path contains the start point at the beginning
102      * of the list as well as the end.
103      * @return the sequence of vertices defined by the path
104      */
105     public List<Vector2D> getVertexSequence() {
106         final List<Vector2D> sequence = new ArrayList<>();
107 
108         Vector2D pt;
109 
110         // add the start point, if present
111         pt = getStartVertex();
112         if (pt != null) {
113             sequence.add(pt);
114         }
115 
116         // add end points
117         for (final LineConvexSubset sub : elements) {
118             pt = sub.getEndPoint();
119             if (pt != null) {
120                 sequence.add(pt);
121             }
122         }
123 
124         return sequence;
125     }
126 
127     /** Return true if the path has an element with infinite size.
128      * @return true if the path is infinite
129      */
130     @Override
131     public boolean isInfinite() {
132         return !isEmpty() && (getStartVertex() == null || getEndVertex() == null);
133     }
134 
135     /** Return true if the path has a finite size. This will be true if there are
136      * no elements in the path or if all elements have a finite length.
137      * @return true if the path is finite
138      */
139     @Override
140     public boolean isFinite() {
141         return !isInfinite();
142     }
143 
144     /** {@inheritDoc}
145      *
146      * <p>The size of the path is defined as the sum of the sizes (lengths) of all path elements.</p>
147      */
148     @Override
149     public double getSize() {
150         double sum = 0.0;
151         for (final LineConvexSubset element : elements) {
152             sum += element.getSize();
153         }
154 
155         return sum;
156     }
157 
158     /** Return true if the path does not contain any elements.
159      * @return true if the path does not contain any elements
160      */
161     public boolean isEmpty() {
162         return elements.isEmpty();
163     }
164 
165     /** Return true if the path is closed, meaning that the end point for the last
166      * element is equivalent to the start point of the first.
167      * @return true if the end point for the last element is equivalent to the
168      *      start point for the first
169      */
170     public boolean isClosed() {
171         final LineConvexSubset endElement = getEnd();
172 
173         if (endElement != null) {
174             final Vector2D start = getStartVertex();
175             final Vector2D end = endElement.getEndPoint();
176 
177             return start != null && end != null && start.eq(end, endElement.getPrecision());
178         }
179 
180         return false;
181     }
182 
183     /** Transform this instance with the argument, returning the result in a new instance.
184      * @param transform the transform to apply
185      * @return a new instance, transformed by the argument
186      */
187     public LinePath transform(final Transform<Vector2D> transform) {
188         if (!isEmpty()) {
189             final List<LineConvexSubset> transformed = elements.stream()
190                 .map(s -> s.transform(transform))
191                 .collect(Collectors.toCollection(ArrayList::new));
192 
193             return new LinePath(transformed);
194         }
195 
196         return this;
197     }
198 
199     /** Return a new instance with all line subset directions, and their order,
200      * reversed. The last line subset in this instance will be the first in the
201      * returned instance.
202      * @return a new instance with the path reversed
203      */
204     public LinePath reverse() {
205         if (!isEmpty()) {
206             final List<LineConvexSubset> reversed = elements.stream()
207                 .map(LineConvexSubset::reverse)
208                 .collect(Collectors.toCollection(ArrayList::new));
209             Collections.reverse(reversed);
210 
211             return new LinePath(reversed);
212         }
213 
214         return this;
215     }
216 
217     /** Simplify this path, if possible, by combining adjacent elements that lie on the
218      * same line (as determined by {@link Line#equals(Object)}).
219      * @return a simplified instance
220      */
221     public LinePath simplify() {
222         final List<LineConvexSubset> simplified = new ArrayList<>();
223 
224         final int size = elements.size();
225 
226         LineConvexSubset current;
227         Line currentLine;
228         double end;
229 
230         int idx = 0;
231         int testIdx;
232         while (idx < size) {
233             current = elements.get(idx);
234             currentLine = current.getLine();
235             end = current.getSubspaceEnd();
236 
237             // try to combine with forward neighbors
238             testIdx = idx + 1;
239             while (testIdx < size && currentLine.equals(elements.get(testIdx).getLine())) {
240                 end = Math.max(end, elements.get(testIdx).getSubspaceEnd());
241                 ++testIdx;
242             }
243 
244             if (testIdx > idx + 1) {
245                 // we found something to merge
246                 simplified.add(Lines.subsetFromInterval(currentLine, current.getSubspaceStart(), end));
247             } else {
248                 simplified.add(current);
249             }
250 
251             idx = testIdx;
252         }
253 
254         // combine the first and last items if needed
255         if (isClosed() && simplified.size() > 2 && simplified.get(0).getLine().equals(
256                 simplified.get(simplified.size() - 1).getLine())) {
257 
258             final LineConvexSubset startElement = simplified.get(0);
259             final LineConvexSubset endElement = simplified.remove(simplified.size() - 1);
260 
261             final LineConvexSubset combined = Lines.subsetFromInterval(
262                     endElement.getLine(), endElement.getSubspaceStart(), startElement.getSubspaceEnd());
263 
264             simplified.set(0, combined);
265         }
266 
267         return new SimplifiedLinePath(simplified);
268     }
269 
270     /** Return a string representation of the path.
271      *
272      * <p>In order to keep the string representation short but useful, the exact format of the return
273      * value depends on the properties of the path. See below for examples.
274      *
275      * <ul>
276      *      <li>Empty path
277      *          <ul>
278      *              <li>{@code LinePath[empty= true]}</li>
279      *          </ul>
280      *      </li>
281      *      <li>Single element
282      *          <ul>
283      *              <li>{@code LinePath[single= Segment[startPoint= (0.0, 0.0), endPoint= (1.0, 0.0)]]}</li>
284      *          </ul>
285      *      </li>
286      *      <li>Path with infinite start element
287      *          <ul>
288      *              <li>{@code LinePath[startDirection= (1.0, 0.0), vertices= [(1.0, 0.0), (1.0, 1.0)]]}</li>
289      *          </ul>
290      *      </li>
291      *      <li>Path with infinite end element
292      *          <ul>
293      *              <li>{@code LinePath[vertices= [(0.0, 1.0), (0.0, 0.0)], endDirection= (1.0, 0.0)]}</li>
294      *          </ul>
295      *      </li>
296      *      <li>Path with infinite start and end elements
297      *          <ul>
298      *              <li>{@code LinePath[startDirection= (0.0, 1.0), vertices= [(0.0, 0.0)], endDirection= (1.0, 0.0)]}</li>
299      *          </ul>
300      *      </li>
301      *      <li>Path with no infinite elements
302      *          <ul>
303      *              <li>{@code LinePath[vertices= [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]]}</li>
304      *          </ul>
305      *      </li>
306      * </ul>
307      */
308     @Override
309     public String toString() {
310         final StringBuilder sb = new StringBuilder();
311         sb.append(this.getClass().getSimpleName())
312             .append('[');
313 
314         if (elements.isEmpty()) {
315             sb.append("empty= true");
316         } else if (elements.size() == 1) {
317             sb.append("single= ")
318                 .append(elements.get(0));
319         } else {
320             final LineConvexSubset startElement = getStart();
321             if (startElement.getStartPoint() == null) {
322                 sb.append("startDirection= ")
323                     .append(startElement.getLine().getDirection())
324                     .append(", ");
325             }
326 
327             sb.append("vertexSequence= ")
328                 .append(getVertexSequence());
329 
330             final LineConvexSubset endElement = getEnd();
331             if (endElement.getEndPoint() == null) {
332                 sb.append(", endDirection= ")
333                     .append(endElement.getLine().getDirection());
334             }
335         }
336 
337         sb.append(']');
338 
339         return sb.toString();
340     }
341 
342     /** Get the start vertex for the path or null if the path is empty
343      * or has an infinite start line subset.
344      * @return the start vertex for the path or null if the path does
345      *      not start with a vertex
346      */
347     private Vector2D getStartVertex() {
348         final LineConvexSubset seg = getStart();
349         return (seg != null) ? seg.getStartPoint() : null;
350     }
351 
352     /** Get the end vertex for the path or null if the path is empty
353      * or has an infinite end line subset.
354      * @return the end vertex for the path or null if the path does
355      *      not end with a vertex
356      */
357     private Vector2D getEndVertex() {
358         final LineConvexSubset seg = getEnd();
359         return (seg != null) ? seg.getEndPoint() : null;
360     }
361 
362     /** Build a new path from the given line subsets.
363      * @param subsets the line subsets to comprise the path
364      * @return new path containing the given line subsets in order
365      * @throws IllegalStateException if the line subsets do not form a connected path
366      */
367     public static LinePath from(final LineConvexSubset... subsets) {
368         return from(Arrays.asList(subsets));
369     }
370 
371     /** Build a new path from the given line subsets.
372      * @param subsets the line subsets to comprise the path
373      * @return new path containing the given line subsets in order
374      * @throws IllegalStateException if the subsets do not form a connected path
375      */
376     public static LinePath from(final Collection<? extends LineConvexSubset> subsets) {
377         final Builder builder = builder(null);
378 
379         for (final LineConvexSubset subset : subsets) {
380             builder.append(subset);
381         }
382 
383         return builder.build();
384     }
385 
386     /** Build a new path from the given vertices. A line segment is created
387      * from the last vertex to the first one, if the two vertices are not already
388      * considered equal using the given precision context. This method is equivalent to
389      * calling {@link #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
390      * fromVertices(vertices, true, precision)}
391      * @param vertices the vertices to construct the closed path from
392      * @param precision precision context used to construct the line segment
393      *      instances for the path
394      * @return new closed path constructed from the given vertices
395      * @throws IllegalStateException if {@code vertices} contains only a single unique vertex
396      * @see #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
397      */
398     public static LinePath fromVertexLoop(final Collection<Vector2D> vertices,
399             final Precision.DoubleEquivalence precision) {
400 
401         return fromVertices(vertices, true, precision);
402     }
403 
404     /** Build a new path from the given vertices. No additional segment is added
405      * from the last vertex to the first. This method is equivalent to calling
406      * {@link #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
407      * fromVertices(vertices, false, precision)}.
408      * @param vertices the vertices to construct the path from
409      * @param precision precision context used to construct the line segment
410      *      instances for the path
411      * @return new path constructed from the given vertices
412      * @throws IllegalStateException if {@code vertices} contains only a single unique vertex
413      * @see #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
414      */
415     public static LinePath fromVertices(final Collection<Vector2D> vertices,
416             final Precision.DoubleEquivalence precision) {
417 
418         return fromVertices(vertices, false, precision);
419     }
420 
421     /** Build a new path from the given vertices.
422      * @param vertices the vertices to construct the path from
423      * @param close if true, a line segment is created from the last vertex
424      *      given to the first one, if the two vertices are not already considered
425      *      equal using the given precision context.
426      * @param precision precision context used to construct the line segment
427      *      instances for the path
428      * @return new path constructed from the given vertices
429      * @throws IllegalStateException if {@code vertices} contains only a single unique vertex
430      */
431     public static LinePath fromVertices(final Collection<Vector2D> vertices,
432             final boolean close, final Precision.DoubleEquivalence precision) {
433 
434         return builder(precision)
435                 .appendVertices(vertices)
436                 .build(close);
437     }
438 
439     /** Return a path containing no elements.
440      * @return a path containing no elements
441      */
442     public static LinePath empty() {
443         return EMPTY;
444     }
445 
446     /** Return a {@link Builder} instance configured with the given precision
447      * context. The precision context is used when building line segments from
448      * vertices and may be omitted if raw vertices are not used.
449      * @param precision precision context to use when building line segments from
450      *      raw vertices; may be null if raw vertices are not used.
451      * @return a new {@link Builder} instance
452      */
453     public static Builder builder(final Precision.DoubleEquivalence precision) {
454         return new Builder(precision);
455     }
456 
457     /** Class used to build line paths.
458      */
459     public static final class Builder {
460         /** Line subsets appended to the path. */
461         private List<LineConvexSubset> appended;
462 
463         /** Line subsets prepended to the path. */
464         private List<LineConvexSubset> prepended;
465 
466         /** Precision context used when creating line segments directly from vertices. */
467         private Precision.DoubleEquivalence precision;
468 
469         /** The current vertex at the start of the path. */
470         private Vector2D startVertex;
471 
472         /** The current vertex at the end of the path. */
473         private Vector2D endVertex;
474 
475         /** The precision context used when performing comparisons involving the current
476          * end vertex.
477          */
478         private Precision.DoubleEquivalence endVertexPrecision;
479 
480         /** Construct a new instance configured with the given precision context. The
481          * precision context is used when building line segments from vertices and
482          * may be omitted if raw vertices are not used.
483          * @param precision precision context to use when creating line segments
484          *      from vertices
485          */
486         private Builder(final Precision.DoubleEquivalence precision) {
487             setPrecision(precision);
488         }
489 
490         /** Set the precision context. This context is used only when creating line segments
491          * from appended or prepended vertices. It is not used when adding existing
492          * {@link LineConvexSubset} instances since those contain their own precision contexts.
493          * @param builderPrecision precision context to use when creating line segments
494          *      from vertices
495          * @return this instance
496          */
497         public Builder setPrecision(final Precision.DoubleEquivalence builderPrecision) {
498             this.precision = builderPrecision;
499 
500             return this;
501         }
502 
503         /** Get the line subset at the start of the path or null if it does not exist.
504          * @return the line subset at the start of the path
505          */
506         public LineConvexSubset getStart() {
507             LineConvexSubset start = getLast(prepended);
508             if (start == null) {
509                 start = getFirst(appended);
510             }
511             return start;
512         }
513 
514         /** Get the line subset at the end of the path or null if it does not exist.
515          * @return the line subset at the end of the path
516          */
517         public LineConvexSubset getEnd() {
518             LineConvexSubset end = getLast(appended);
519             if (end == null) {
520                 end = getFirst(prepended);
521             }
522             return end;
523         }
524 
525         /** Append a line subset to the end of the path.
526          * @param subset line subset to append to the path
527          * @return the current builder instance
528          * @throws IllegalStateException if the path contains a previous element
529          *      and the end vertex of the previous element is not equivalent to the
530          *      start vertex of the argument
531          */
532         public Builder append(final LineConvexSubset subset) {
533             validateConnected(getEnd(), subset);
534             appendInternal(subset);
535 
536             return this;
537         }
538 
539         /** Add a vertex to the end of this path. If the path already has an end vertex,
540          * then a line segment is added between the previous end vertex and this vertex,
541          * using the configured precision context.
542          * @param vertex the vertex to add
543          * @return this instance
544          * @see #setPrecision(Precision.DoubleEquivalence)
545          */
546         public Builder append(final Vector2D vertex) {
547             final Precision.DoubleEquivalence vertexPrecision = getAddVertexPrecision();
548 
549             if (endVertex == null) {
550                 // make sure that we're not adding to an infinite element
551                 final LineConvexSubset end = getEnd();
552                 if (end != null) {
553                     throw new IllegalStateException(
554                             MessageFormat.format("Cannot add vertex {0} after infinite line subset: {1}",
555                                     vertex, end));
556                 }
557 
558                 // this is the first vertex added
559                 startVertex = vertex;
560                 endVertex = vertex;
561                 endVertexPrecision = vertexPrecision;
562             } else if (!endVertex.eq(vertex, endVertexPrecision)) {
563                 // only add the vertex if its not equal to the end point
564                 // of the last element
565                 appendInternal(Lines.segmentFromPoints(endVertex, vertex, endVertexPrecision));
566             }
567 
568             return this;
569         }
570 
571         /** Convenience method for appending a collection of vertices to the path in a single method call.
572          * @param vertices the vertices to append
573          * @return this instance
574          * @see #append(Vector2D)
575          */
576         public Builder appendVertices(final Collection<? extends Vector2D> vertices) {
577             for (final Vector2D vertex : vertices) {
578                 append(vertex);
579             }
580 
581             return this;
582         }
583 
584         /** Convenience method for appending multiple vertices to the path at once.
585          * @param vertices the vertices to append
586          * @return this instance
587          * @see #append(Vector2D)
588          */
589         public Builder appendVertices(final Vector2D... vertices) {
590             return appendVertices(Arrays.asList(vertices));
591         }
592 
593         /** Prepend a line subset to the beginning of the path.
594          * @param subset line subset to prepend to the path
595          * @return the current builder instance
596          * @throws IllegalStateException if the path contains a start element
597          *      and the end vertex of the argument is not equivalent to the
598          *      start vertex of the start element.
599          */
600         public Builder prepend(final LineConvexSubset subset) {
601             validateConnected(subset, getStart());
602             prependInternal(subset);
603 
604             return this;
605         }
606 
607         /** Add a vertex to the front of this path. If the path already has a start vertex,
608          * then a line segment is added between this vertex and the previous start vertex,
609          * using the configured precision context.
610          * @param vertex the vertex to add
611          * @return this instance
612          * @see #setPrecision(Precision.DoubleEquivalence)
613          */
614         public Builder prepend(final Vector2D vertex) {
615             final Precision.DoubleEquivalence vertexPrecision = getAddVertexPrecision();
616 
617             if (startVertex == null) {
618                 // make sure that we're not adding to an infinite element
619                 final LineConvexSubset start = getStart();
620                 if (start != null) {
621                     throw new IllegalStateException(
622                             MessageFormat.format("Cannot add vertex {0} before infinite line subset: {1}",
623                                     vertex, start));
624                 }
625 
626                 // this is the first vertex added
627                 startVertex = vertex;
628                 endVertex = vertex;
629                 endVertexPrecision = vertexPrecision;
630             } else if (!vertex.eq(startVertex, vertexPrecision)) {
631                 // only add if the vertex is not equal to the start
632                 // point of the first element
633                 prependInternal(Lines.segmentFromPoints(vertex, startVertex, vertexPrecision));
634             }
635 
636             return this;
637         }
638 
639         /** Convenience method for prepending a collection of vertices to the path in a single method call.
640          * The vertices are logically prepended as a single group, meaning that the first vertex
641          * in the given collection appears as the first vertex in the path after this method call.
642          * Internally, this means that the vertices are actually passed to the {@link #prepend(Vector2D)}
643          * method in reverse order.
644          * @param vertices the vertices to prepend
645          * @return this instance
646          * @see #prepend(Vector2D)
647          */
648         public Builder prependVertices(final Collection<Vector2D> vertices) {
649             return prependVertices(vertices.toArray(new Vector2D[0]));
650         }
651 
652         /** Convenience method for prepending multiple vertices to the path in a single method call.
653          * The vertices are logically prepended as a single group, meaning that the first vertex
654          * in the given collection appears as the first vertex in the path after this method call.
655          * Internally, this means that the vertices are actually passed to the {@link #prepend(Vector2D)}
656          * method in reverse order.
657          * @param vertices the vertices to prepend
658          * @return this instance
659          * @see #prepend(Vector2D)
660          */
661         public Builder prependVertices(final Vector2D... vertices) {
662             for (int i = vertices.length - 1; i >= 0; --i) {
663                 prepend(vertices[i]);
664             }
665 
666             return this;
667         }
668 
669         /** Close the current path and build a new {@link LinePath} instance.  This method is equivalent
670          * to {@code builder.build(true)}.
671          * @return new closed path instance
672          * @throws IllegalStateException if the builder was given only a single unique vertex
673          */
674         public LinePath close() {
675             return build(true);
676         }
677 
678         /** Build a {@link LinePath} instance from the configured path. This method is equivalent
679          * to {@code builder.build(false)}.
680          * @return new path instance
681          * @throws IllegalStateException if the builder was given only a single unique vertex
682          */
683         public LinePath build() {
684             return build(false);
685         }
686 
687         /** Build a {@link LinePath} instance from the configured path.
688          * @param close if true, the path will be closed by adding an end point equivalent to the
689          *      start point
690          * @return new path instance
691          * @throws IllegalStateException if the builder was given only a single unique vertex
692          */
693         public LinePath build(final boolean close) {
694             if (close) {
695                 closePath();
696             }
697 
698             // combine all of the line subsets
699             List<LineConvexSubset> result = null;
700 
701             if (prepended != null) {
702                 result = prepended;
703                 Collections.reverse(result);
704             }
705 
706             if (appended != null) {
707                 if (result == null) {
708                     result = appended;
709                 } else {
710                     result.addAll(appended);
711                 }
712             }
713 
714             if (result == null) {
715                 result = Collections.emptyList();
716             }
717 
718             if (result.isEmpty() && startVertex != null) {
719                 throw new IllegalStateException(
720                         MessageFormat.format("Unable to create line path; only a single unique vertex provided: {0} ",
721                                 startVertex));
722             }
723 
724             // clear internal state
725             appended = null;
726             prepended = null;
727 
728             // build the final path instance, using the shared empty instance if
729             // no line subsets are present
730 
731             return result.isEmpty() ? empty() : new LinePath(result);
732         }
733 
734         /** Close the path by adding an end point equivalent to the path start point.
735          * @throws IllegalStateException if the path cannot be closed
736          */
737         private void closePath() {
738             final LineConvexSubset end = getEnd();
739 
740             if (end != null) {
741                 if (startVertex != null && endVertex != null) {
742                     if (!endVertex.eq(startVertex, endVertexPrecision)) {
743                         appendInternal(Lines.segmentFromPoints(endVertex, startVertex, endVertexPrecision));
744                     }
745                 } else {
746                     throw new IllegalStateException("Unable to close line path: line path is infinite");
747                 }
748             }
749         }
750 
751         /** Validate that the given line subsets  are connected, meaning that the end vertex of {@code previous}
752          * is equivalent to the start vertex of {@code next}. The line subsets are considered valid if either
753          * line subset is null.
754          * @param previous previous line subset
755          * @param next next line subset
756          * @throws IllegalStateException if previous and next are not null and the end vertex of previous
757          *      is not equivalent the start vertex of next
758          */
759         private void validateConnected(final LineConvexSubset previous, final LineConvexSubset next) {
760             if (previous != null && next != null) {
761                 final Vector2D nextStartVertex = next.getStartPoint();
762                 final Vector2D previousEndVertex = previous.getEndPoint();
763                 final Precision.DoubleEquivalence previousPrecision = previous.getPrecision();
764 
765                 if (nextStartVertex == null || previousEndVertex == null ||
766                         !(nextStartVertex.eq(previousEndVertex, previousPrecision))) {
767 
768                     throw new IllegalStateException(
769                             MessageFormat.format("Path line subsets are not connected: previous= {0}, next= {1}",
770                                     previous, next));
771                 }
772             }
773         }
774 
775         /** Get the precision context used when adding raw vertices to the path. An exception is thrown
776          * if no precision has been specified.
777          * @return the precision context used when creating working with raw vertices
778          * @throws IllegalStateException if no precision context is configured
779          */
780         private Precision.DoubleEquivalence getAddVertexPrecision() {
781             if (precision == null) {
782                 throw new IllegalStateException("Unable to create line segment: no vertex precision specified");
783             }
784 
785             return precision;
786         }
787 
788         /** Append the given, validated line subsets to the path.
789          * @param subset validated line subset to append
790          */
791         private void appendInternal(final LineConvexSubset subset) {
792             if (appended == null) {
793                 appended = new ArrayList<>();
794             }
795 
796             if (appended.isEmpty() &&
797                     (prepended == null || prepended.isEmpty())) {
798                 startVertex = subset.getStartPoint();
799             }
800 
801             endVertex = subset.getEndPoint();
802             endVertexPrecision = subset.getPrecision();
803 
804             appended.add(subset);
805         }
806 
807         /** Prepend the given, validated line subset to the path.
808          * @param subset validated line subset to prepend
809          */
810         private void prependInternal(final LineConvexSubset subset) {
811             if (prepended == null) {
812                 prepended = new ArrayList<>();
813             }
814 
815             startVertex = subset.getStartPoint();
816 
817             if (prepended.isEmpty() &&
818                     (appended == null || appended.isEmpty())) {
819                 endVertex = subset.getEndPoint();
820                 endVertexPrecision = subset.getPrecision();
821             }
822 
823             prepended.add(subset);
824         }
825 
826         /** Get the first element in the list or null if the list is null
827          * or empty.
828          * @param list the list to return the first item from
829          * @return the first item from the given list or null if it does not exist
830          */
831         private LineConvexSubset getFirst(final List<? extends LineConvexSubset> list) {
832             if (list != null && !list.isEmpty()) {
833                 return list.get(0);
834             }
835             return null;
836         }
837 
838         /** Get the last element in the list or null if the list is null
839          * or empty.
840          * @param list the list to return the last item from
841          * @return the last item from the given list or null if it does not exist
842          */
843         private LineConvexSubset getLast(final List<? extends LineConvexSubset> list) {
844             if (list != null && !list.isEmpty()) {
845                 return list.get(list.size() - 1);
846             }
847             return null;
848         }
849     }
850 
851     /** Internal class returned when a line path is simplified to remove unnecessary line subset divisions.
852      * The {@link #simplify()} method on this class simply returns the same instance.
853      */
854     private static final class SimplifiedLinePath extends LinePath {
855         /** Create a new instance containing the given line subsets. No validation is
856          * performed on the inputs. Caller must ensure that the given line subsets represent
857          * a valid, simplified path.
858          * @param elements line subsets comprising the path
859          */
860         private SimplifiedLinePath(final List<LineConvexSubset> elements) {
861             super(elements);
862         }
863 
864         /** {@inheritDoc} */
865         @Override
866         public SimplifiedLinePath simplify() {
867             // already simplified
868             return this;
869         }
870     }
871 }