LinePath.java

  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. import java.text.MessageFormat;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.stream.Collectors;
  25. import java.util.stream.Stream;

  26. import org.apache.commons.geometry.core.Sized;
  27. import org.apache.commons.geometry.core.Transform;
  28. import org.apache.commons.geometry.euclidean.twod.BoundarySource2D;
  29. import org.apache.commons.geometry.euclidean.twod.Line;
  30. import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
  31. import org.apache.commons.geometry.euclidean.twod.Lines;
  32. import org.apache.commons.geometry.euclidean.twod.Vector2D;
  33. import org.apache.commons.numbers.core.Precision;

  34. /** Class representing a connected path of {@link LineConvexSubset line convex subsets}.
  35.  * The elements in the path are connected end to end, with the end vertex of the previous
  36.  * element equivalent to the start vertex of the next element. Elements are not required to
  37.  * be finite. However, since path elements are connected, only the first element and/or last
  38.  * element may be infinite.
  39.  *
  40.  * <p>Instances of this class are guaranteed to be immutable.</p>
  41.  */
  42. public class LinePath implements BoundarySource2D, Sized {
  43.     /** Line path instance containing no elements. */
  44.     private static final LinePath EMPTY = new LinePath(Collections.emptyList());

  45.     /** The line convex subsets comprising the path. */
  46.     private final List<LineConvexSubset> elements;

  47.     /** Simple constructor. Callers are responsible for ensuring that the given list of
  48.      * line subsets defines a valid path. No validation is performed.
  49.      * @param elements elements defining the path.
  50.      */
  51.     LinePath(final List<LineConvexSubset> elements) {
  52.         this.elements = Collections.unmodifiableList(elements);
  53.     }

  54.     /** {@inheritDoc} */
  55.     @Override
  56.     public Stream<LineConvexSubset> boundaryStream() {
  57.         return getElements().stream();
  58.     }

  59.     /** Get the sequence of line subsets comprising the path.
  60.      * @return the sequence of line subsets comprising the path
  61.      */
  62.     public List<LineConvexSubset> getElements() {
  63.         return elements;
  64.     }

  65.     /** Get the line subset at the start of the path or null if the path is empty. If the
  66.      * path consists of a single line subset, then the returned instance with be the same
  67.      * as that returned by {@link #getEnd()}.
  68.      * @return the line subset at the start of the path or null if the path is empty
  69.      * @see #getEnd()
  70.      */
  71.     public LineConvexSubset getStart() {
  72.         if (!isEmpty()) {
  73.             return elements.get(0);
  74.         }
  75.         return null;
  76.     }

  77.     /** Get the line subset at the end of the path or null if the path is empty. If the
  78.      * path consists of a single line subset, then the returned instance with be the same
  79.      * as that returned by {@link #getStart()}.
  80.      * @return the line subset at the end of the path or null if the path is empty
  81.      * @see #getStart()
  82.      */
  83.     public LineConvexSubset getEnd() {
  84.         if (!isEmpty()) {
  85.             return elements.get(elements.size() - 1);
  86.         }
  87.         return null;
  88.     }

  89.     /** Get the sequence of vertices defined by the path. Vertices appear in the
  90.      * list as many times as they are visited in the path. For example, the vertex
  91.      * sequence for a closed path contains the start point at the beginning
  92.      * of the list as well as the end.
  93.      * @return the sequence of vertices defined by the path
  94.      */
  95.     public List<Vector2D> getVertexSequence() {
  96.         final List<Vector2D> sequence = new ArrayList<>();

  97.         Vector2D pt;

  98.         // add the start point, if present
  99.         pt = getStartVertex();
  100.         if (pt != null) {
  101.             sequence.add(pt);
  102.         }

  103.         // add end points
  104.         for (final LineConvexSubset sub : elements) {
  105.             pt = sub.getEndPoint();
  106.             if (pt != null) {
  107.                 sequence.add(pt);
  108.             }
  109.         }

  110.         return sequence;
  111.     }

  112.     /** Return true if the path has an element with infinite size.
  113.      * @return true if the path is infinite
  114.      */
  115.     @Override
  116.     public boolean isInfinite() {
  117.         return !isEmpty() && (getStartVertex() == null || getEndVertex() == null);
  118.     }

  119.     /** Return true if the path has a finite size. This will be true if there are
  120.      * no elements in the path or if all elements have a finite length.
  121.      * @return true if the path is finite
  122.      */
  123.     @Override
  124.     public boolean isFinite() {
  125.         return !isInfinite();
  126.     }

  127.     /** {@inheritDoc}
  128.      *
  129.      * <p>The size of the path is defined as the sum of the sizes (lengths) of all path elements.</p>
  130.      */
  131.     @Override
  132.     public double getSize() {
  133.         double sum = 0.0;
  134.         for (final LineConvexSubset element : elements) {
  135.             sum += element.getSize();
  136.         }

  137.         return sum;
  138.     }

  139.     /** Return true if the path does not contain any elements.
  140.      * @return true if the path does not contain any elements
  141.      */
  142.     public boolean isEmpty() {
  143.         return elements.isEmpty();
  144.     }

  145.     /** Return true if the path is closed, meaning that the end point for the last
  146.      * element is equivalent to the start point of the first.
  147.      * @return true if the end point for the last element is equivalent to the
  148.      *      start point for the first
  149.      */
  150.     public boolean isClosed() {
  151.         final LineConvexSubset endElement = getEnd();

  152.         if (endElement != null) {
  153.             final Vector2D start = getStartVertex();
  154.             final Vector2D end = endElement.getEndPoint();

  155.             return start != null && end != null && start.eq(end, endElement.getPrecision());
  156.         }

  157.         return false;
  158.     }

  159.     /** Transform this instance with the argument, returning the result in a new instance.
  160.      * @param transform the transform to apply
  161.      * @return a new instance, transformed by the argument
  162.      */
  163.     public LinePath transform(final Transform<Vector2D> transform) {
  164.         if (!isEmpty()) {
  165.             final List<LineConvexSubset> transformed = elements.stream()
  166.                 .map(s -> s.transform(transform))
  167.                 .collect(Collectors.toCollection(ArrayList::new));

  168.             return new LinePath(transformed);
  169.         }

  170.         return this;
  171.     }

  172.     /** Return a new instance with all line subset directions, and their order,
  173.      * reversed. The last line subset in this instance will be the first in the
  174.      * returned instance.
  175.      * @return a new instance with the path reversed
  176.      */
  177.     public LinePath reverse() {
  178.         if (!isEmpty()) {
  179.             final List<LineConvexSubset> reversed = elements.stream()
  180.                 .map(LineConvexSubset::reverse)
  181.                 .collect(Collectors.toCollection(ArrayList::new));
  182.             Collections.reverse(reversed);

  183.             return new LinePath(reversed);
  184.         }

  185.         return this;
  186.     }

  187.     /** Simplify this path, if possible, by combining adjacent elements that lie on the
  188.      * same line (as determined by {@link Line#equals(Object)}).
  189.      * @return a simplified instance
  190.      */
  191.     public LinePath simplify() {
  192.         final List<LineConvexSubset> simplified = new ArrayList<>();

  193.         final int size = elements.size();

  194.         LineConvexSubset current;
  195.         Line currentLine;
  196.         double end;

  197.         int idx = 0;
  198.         int testIdx;
  199.         while (idx < size) {
  200.             current = elements.get(idx);
  201.             currentLine = current.getLine();
  202.             end = current.getSubspaceEnd();

  203.             // try to combine with forward neighbors
  204.             testIdx = idx + 1;
  205.             while (testIdx < size && currentLine.equals(elements.get(testIdx).getLine())) {
  206.                 end = Math.max(end, elements.get(testIdx).getSubspaceEnd());
  207.                 ++testIdx;
  208.             }

  209.             if (testIdx > idx + 1) {
  210.                 // we found something to merge
  211.                 simplified.add(Lines.subsetFromInterval(currentLine, current.getSubspaceStart(), end));
  212.             } else {
  213.                 simplified.add(current);
  214.             }

  215.             idx = testIdx;
  216.         }

  217.         // combine the first and last items if needed
  218.         if (isClosed() && simplified.size() > 2 && simplified.get(0).getLine().equals(
  219.                 simplified.get(simplified.size() - 1).getLine())) {

  220.             final LineConvexSubset startElement = simplified.get(0);
  221.             final LineConvexSubset endElement = simplified.remove(simplified.size() - 1);

  222.             final LineConvexSubset combined = Lines.subsetFromInterval(
  223.                     endElement.getLine(), endElement.getSubspaceStart(), startElement.getSubspaceEnd());

  224.             simplified.set(0, combined);
  225.         }

  226.         return new SimplifiedLinePath(simplified);
  227.     }

  228.     /** Return a string representation of the path.
  229.      *
  230.      * <p>In order to keep the string representation short but useful, the exact format of the return
  231.      * value depends on the properties of the path. See below for examples.
  232.      *
  233.      * <ul>
  234.      *      <li>Empty path
  235.      *          <ul>
  236.      *              <li>{@code LinePath[empty= true]}</li>
  237.      *          </ul>
  238.      *      </li>
  239.      *      <li>Single element
  240.      *          <ul>
  241.      *              <li>{@code LinePath[single= Segment[startPoint= (0.0, 0.0), endPoint= (1.0, 0.0)]]}</li>
  242.      *          </ul>
  243.      *      </li>
  244.      *      <li>Path with infinite start element
  245.      *          <ul>
  246.      *              <li>{@code LinePath[startDirection= (1.0, 0.0), vertices= [(1.0, 0.0), (1.0, 1.0)]]}</li>
  247.      *          </ul>
  248.      *      </li>
  249.      *      <li>Path with infinite end element
  250.      *          <ul>
  251.      *              <li>{@code LinePath[vertices= [(0.0, 1.0), (0.0, 0.0)], endDirection= (1.0, 0.0)]}</li>
  252.      *          </ul>
  253.      *      </li>
  254.      *      <li>Path with infinite start and end elements
  255.      *          <ul>
  256.      *              <li>{@code LinePath[startDirection= (0.0, 1.0), vertices= [(0.0, 0.0)], endDirection= (1.0, 0.0)]}</li>
  257.      *          </ul>
  258.      *      </li>
  259.      *      <li>Path with no infinite elements
  260.      *          <ul>
  261.      *              <li>{@code LinePath[vertices= [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]]}</li>
  262.      *          </ul>
  263.      *      </li>
  264.      * </ul>
  265.      */
  266.     @Override
  267.     public String toString() {
  268.         final StringBuilder sb = new StringBuilder();
  269.         sb.append(this.getClass().getSimpleName())
  270.             .append('[');

  271.         if (elements.isEmpty()) {
  272.             sb.append("empty= true");
  273.         } else if (elements.size() == 1) {
  274.             sb.append("single= ")
  275.                 .append(elements.get(0));
  276.         } else {
  277.             final LineConvexSubset startElement = getStart();
  278.             if (startElement.getStartPoint() == null) {
  279.                 sb.append("startDirection= ")
  280.                     .append(startElement.getLine().getDirection())
  281.                     .append(", ");
  282.             }

  283.             sb.append("vertexSequence= ")
  284.                 .append(getVertexSequence());

  285.             final LineConvexSubset endElement = getEnd();
  286.             if (endElement.getEndPoint() == null) {
  287.                 sb.append(", endDirection= ")
  288.                     .append(endElement.getLine().getDirection());
  289.             }
  290.         }

  291.         sb.append(']');

  292.         return sb.toString();
  293.     }

  294.     /** Get the start vertex for the path or null if the path is empty
  295.      * or has an infinite start line subset.
  296.      * @return the start vertex for the path or null if the path does
  297.      *      not start with a vertex
  298.      */
  299.     private Vector2D getStartVertex() {
  300.         final LineConvexSubset seg = getStart();
  301.         return (seg != null) ? seg.getStartPoint() : null;
  302.     }

  303.     /** Get the end vertex for the path or null if the path is empty
  304.      * or has an infinite end line subset.
  305.      * @return the end vertex for the path or null if the path does
  306.      *      not end with a vertex
  307.      */
  308.     private Vector2D getEndVertex() {
  309.         final LineConvexSubset seg = getEnd();
  310.         return (seg != null) ? seg.getEndPoint() : null;
  311.     }

  312.     /** Build a new path from the given line subsets.
  313.      * @param subsets the line subsets to comprise the path
  314.      * @return new path containing the given line subsets in order
  315.      * @throws IllegalStateException if the line subsets do not form a connected path
  316.      */
  317.     public static LinePath from(final LineConvexSubset... subsets) {
  318.         return from(Arrays.asList(subsets));
  319.     }

  320.     /** Build a new path from the given line subsets.
  321.      * @param subsets the line subsets to comprise the path
  322.      * @return new path containing the given line subsets in order
  323.      * @throws IllegalStateException if the subsets do not form a connected path
  324.      */
  325.     public static LinePath from(final Collection<? extends LineConvexSubset> subsets) {
  326.         final Builder builder = builder(null);

  327.         for (final LineConvexSubset subset : subsets) {
  328.             builder.append(subset);
  329.         }

  330.         return builder.build();
  331.     }

  332.     /** Build a new path from the given vertices. A line segment is created
  333.      * from the last vertex to the first one, if the two vertices are not already
  334.      * considered equal using the given precision context. This method is equivalent to
  335.      * calling {@link #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
  336.      * fromVertices(vertices, true, precision)}
  337.      * @param vertices the vertices to construct the closed path from
  338.      * @param precision precision context used to construct the line segment
  339.      *      instances for the path
  340.      * @return new closed path constructed from the given vertices
  341.      * @throws IllegalStateException if {@code vertices} contains only a single unique vertex
  342.      * @see #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
  343.      */
  344.     public static LinePath fromVertexLoop(final Collection<Vector2D> vertices,
  345.             final Precision.DoubleEquivalence precision) {

  346.         return fromVertices(vertices, true, precision);
  347.     }

  348.     /** Build a new path from the given vertices. No additional segment is added
  349.      * from the last vertex to the first. This method is equivalent to calling
  350.      * {@link #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
  351.      * fromVertices(vertices, false, precision)}.
  352.      * @param vertices the vertices to construct the path from
  353.      * @param precision precision context used to construct the line segment
  354.      *      instances for the path
  355.      * @return new path constructed from the given vertices
  356.      * @throws IllegalStateException if {@code vertices} contains only a single unique vertex
  357.      * @see #fromVertices(Collection, boolean, Precision.DoubleEquivalence)
  358.      */
  359.     public static LinePath fromVertices(final Collection<Vector2D> vertices,
  360.             final Precision.DoubleEquivalence precision) {

  361.         return fromVertices(vertices, false, precision);
  362.     }

  363.     /** Build a new path from the given vertices.
  364.      * @param vertices the vertices to construct the path from
  365.      * @param close if true, a line segment is created from the last vertex
  366.      *      given to the first one, if the two vertices are not already considered
  367.      *      equal using the given precision context.
  368.      * @param precision precision context used to construct the line segment
  369.      *      instances for the path
  370.      * @return new path constructed from the given vertices
  371.      * @throws IllegalStateException if {@code vertices} contains only a single unique vertex
  372.      */
  373.     public static LinePath fromVertices(final Collection<Vector2D> vertices,
  374.             final boolean close, final Precision.DoubleEquivalence precision) {

  375.         return builder(precision)
  376.                 .appendVertices(vertices)
  377.                 .build(close);
  378.     }

  379.     /** Return a path containing no elements.
  380.      * @return a path containing no elements
  381.      */
  382.     public static LinePath empty() {
  383.         return EMPTY;
  384.     }

  385.     /** Return a {@link Builder} instance configured with the given precision
  386.      * context. The precision context is used when building line segments from
  387.      * vertices and may be omitted if raw vertices are not used.
  388.      * @param precision precision context to use when building line segments from
  389.      *      raw vertices; may be null if raw vertices are not used.
  390.      * @return a new {@link Builder} instance
  391.      */
  392.     public static Builder builder(final Precision.DoubleEquivalence precision) {
  393.         return new Builder(precision);
  394.     }

  395.     /** Class used to build line paths.
  396.      */
  397.     public static final class Builder {
  398.         /** Line subsets appended to the path. */
  399.         private List<LineConvexSubset> appended;

  400.         /** Line subsets prepended to the path. */
  401.         private List<LineConvexSubset> prepended;

  402.         /** Precision context used when creating line segments directly from vertices. */
  403.         private Precision.DoubleEquivalence precision;

  404.         /** The current vertex at the start of the path. */
  405.         private Vector2D startVertex;

  406.         /** The current vertex at the end of the path. */
  407.         private Vector2D endVertex;

  408.         /** The precision context used when performing comparisons involving the current
  409.          * end vertex.
  410.          */
  411.         private Precision.DoubleEquivalence endVertexPrecision;

  412.         /** Construct a new instance configured with the given precision context. The
  413.          * precision context is used when building line segments from vertices and
  414.          * may be omitted if raw vertices are not used.
  415.          * @param precision precision context to use when creating line segments
  416.          *      from vertices
  417.          */
  418.         private Builder(final Precision.DoubleEquivalence precision) {
  419.             setPrecision(precision);
  420.         }

  421.         /** Set the precision context. This context is used only when creating line segments
  422.          * from appended or prepended vertices. It is not used when adding existing
  423.          * {@link LineConvexSubset} instances since those contain their own precision contexts.
  424.          * @param builderPrecision precision context to use when creating line segments
  425.          *      from vertices
  426.          * @return this instance
  427.          */
  428.         public Builder setPrecision(final Precision.DoubleEquivalence builderPrecision) {
  429.             this.precision = builderPrecision;

  430.             return this;
  431.         }

  432.         /** Get the line subset at the start of the path or null if it does not exist.
  433.          * @return the line subset at the start of the path
  434.          */
  435.         public LineConvexSubset getStart() {
  436.             LineConvexSubset start = getLast(prepended);
  437.             if (start == null) {
  438.                 start = getFirst(appended);
  439.             }
  440.             return start;
  441.         }

  442.         /** Get the line subset at the end of the path or null if it does not exist.
  443.          * @return the line subset at the end of the path
  444.          */
  445.         public LineConvexSubset getEnd() {
  446.             LineConvexSubset end = getLast(appended);
  447.             if (end == null) {
  448.                 end = getFirst(prepended);
  449.             }
  450.             return end;
  451.         }

  452.         /** Append a line subset to the end of the path.
  453.          * @param subset line subset to append to the path
  454.          * @return the current builder instance
  455.          * @throws IllegalStateException if the path contains a previous element
  456.          *      and the end vertex of the previous element is not equivalent to the
  457.          *      start vertex of the argument
  458.          */
  459.         public Builder append(final LineConvexSubset subset) {
  460.             validateConnected(getEnd(), subset);
  461.             appendInternal(subset);

  462.             return this;
  463.         }

  464.         /** Add a vertex to the end of this path. If the path already has an end vertex,
  465.          * then a line segment is added between the previous end vertex and this vertex,
  466.          * using the configured precision context.
  467.          * @param vertex the vertex to add
  468.          * @return this instance
  469.          * @see #setPrecision(Precision.DoubleEquivalence)
  470.          */
  471.         public Builder append(final Vector2D vertex) {
  472.             final Precision.DoubleEquivalence vertexPrecision = getAddVertexPrecision();

  473.             if (endVertex == null) {
  474.                 // make sure that we're not adding to an infinite element
  475.                 final LineConvexSubset end = getEnd();
  476.                 if (end != null) {
  477.                     throw new IllegalStateException(
  478.                             MessageFormat.format("Cannot add vertex {0} after infinite line subset: {1}",
  479.                                     vertex, end));
  480.                 }

  481.                 // this is the first vertex added
  482.                 startVertex = vertex;
  483.                 endVertex = vertex;
  484.                 endVertexPrecision = vertexPrecision;
  485.             } else if (!endVertex.eq(vertex, endVertexPrecision)) {
  486.                 // only add the vertex if its not equal to the end point
  487.                 // of the last element
  488.                 appendInternal(Lines.segmentFromPoints(endVertex, vertex, endVertexPrecision));
  489.             }

  490.             return this;
  491.         }

  492.         /** Convenience method for appending a collection of vertices to the path in a single method call.
  493.          * @param vertices the vertices to append
  494.          * @return this instance
  495.          * @see #append(Vector2D)
  496.          */
  497.         public Builder appendVertices(final Collection<? extends Vector2D> vertices) {
  498.             for (final Vector2D vertex : vertices) {
  499.                 append(vertex);
  500.             }

  501.             return this;
  502.         }

  503.         /** Convenience method for appending multiple vertices to the path at once.
  504.          * @param vertices the vertices to append
  505.          * @return this instance
  506.          * @see #append(Vector2D)
  507.          */
  508.         public Builder appendVertices(final Vector2D... vertices) {
  509.             return appendVertices(Arrays.asList(vertices));
  510.         }

  511.         /** Prepend a line subset to the beginning of the path.
  512.          * @param subset line subset to prepend to the path
  513.          * @return the current builder instance
  514.          * @throws IllegalStateException if the path contains a start element
  515.          *      and the end vertex of the argument is not equivalent to the
  516.          *      start vertex of the start element.
  517.          */
  518.         public Builder prepend(final LineConvexSubset subset) {
  519.             validateConnected(subset, getStart());
  520.             prependInternal(subset);

  521.             return this;
  522.         }

  523.         /** Add a vertex to the front of this path. If the path already has a start vertex,
  524.          * then a line segment is added between this vertex and the previous start vertex,
  525.          * using the configured precision context.
  526.          * @param vertex the vertex to add
  527.          * @return this instance
  528.          * @see #setPrecision(Precision.DoubleEquivalence)
  529.          */
  530.         public Builder prepend(final Vector2D vertex) {
  531.             final Precision.DoubleEquivalence vertexPrecision = getAddVertexPrecision();

  532.             if (startVertex == null) {
  533.                 // make sure that we're not adding to an infinite element
  534.                 final LineConvexSubset start = getStart();
  535.                 if (start != null) {
  536.                     throw new IllegalStateException(
  537.                             MessageFormat.format("Cannot add vertex {0} before infinite line subset: {1}",
  538.                                     vertex, start));
  539.                 }

  540.                 // this is the first vertex added
  541.                 startVertex = vertex;
  542.                 endVertex = vertex;
  543.                 endVertexPrecision = vertexPrecision;
  544.             } else if (!vertex.eq(startVertex, vertexPrecision)) {
  545.                 // only add if the vertex is not equal to the start
  546.                 // point of the first element
  547.                 prependInternal(Lines.segmentFromPoints(vertex, startVertex, vertexPrecision));
  548.             }

  549.             return this;
  550.         }

  551.         /** Convenience method for prepending a collection of vertices to the path in a single method call.
  552.          * The vertices are logically prepended as a single group, meaning that the first vertex
  553.          * in the given collection appears as the first vertex in the path after this method call.
  554.          * Internally, this means that the vertices are actually passed to the {@link #prepend(Vector2D)}
  555.          * method in reverse order.
  556.          * @param vertices the vertices to prepend
  557.          * @return this instance
  558.          * @see #prepend(Vector2D)
  559.          */
  560.         public Builder prependVertices(final Collection<Vector2D> vertices) {
  561.             return prependVertices(vertices.toArray(new Vector2D[0]));
  562.         }

  563.         /** Convenience method for prepending multiple vertices to the path in a single method call.
  564.          * The vertices are logically prepended as a single group, meaning that the first vertex
  565.          * in the given collection appears as the first vertex in the path after this method call.
  566.          * Internally, this means that the vertices are actually passed to the {@link #prepend(Vector2D)}
  567.          * method in reverse order.
  568.          * @param vertices the vertices to prepend
  569.          * @return this instance
  570.          * @see #prepend(Vector2D)
  571.          */
  572.         public Builder prependVertices(final Vector2D... vertices) {
  573.             for (int i = vertices.length - 1; i >= 0; --i) {
  574.                 prepend(vertices[i]);
  575.             }

  576.             return this;
  577.         }

  578.         /** Close the current path and build a new {@link LinePath} instance.  This method is equivalent
  579.          * to {@code builder.build(true)}.
  580.          * @return new closed path instance
  581.          * @throws IllegalStateException if the builder was given only a single unique vertex
  582.          */
  583.         public LinePath close() {
  584.             return build(true);
  585.         }

  586.         /** Build a {@link LinePath} instance from the configured path. This method is equivalent
  587.          * to {@code builder.build(false)}.
  588.          * @return new path instance
  589.          * @throws IllegalStateException if the builder was given only a single unique vertex
  590.          */
  591.         public LinePath build() {
  592.             return build(false);
  593.         }

  594.         /** Build a {@link LinePath} instance from the configured path.
  595.          * @param close if true, the path will be closed by adding an end point equivalent to the
  596.          *      start point
  597.          * @return new path instance
  598.          * @throws IllegalStateException if the builder was given only a single unique vertex
  599.          */
  600.         public LinePath build(final boolean close) {
  601.             if (close) {
  602.                 closePath();
  603.             }

  604.             // combine all of the line subsets
  605.             List<LineConvexSubset> result = null;

  606.             if (prepended != null) {
  607.                 result = prepended;
  608.                 Collections.reverse(result);
  609.             }

  610.             if (appended != null) {
  611.                 if (result == null) {
  612.                     result = appended;
  613.                 } else {
  614.                     result.addAll(appended);
  615.                 }
  616.             }

  617.             if (result == null) {
  618.                 result = Collections.emptyList();
  619.             }

  620.             if (result.isEmpty() && startVertex != null) {
  621.                 throw new IllegalStateException(
  622.                         MessageFormat.format("Unable to create line path; only a single unique vertex provided: {0} ",
  623.                                 startVertex));
  624.             }

  625.             // clear internal state
  626.             appended = null;
  627.             prepended = null;

  628.             // build the final path instance, using the shared empty instance if
  629.             // no line subsets are present

  630.             return result.isEmpty() ? empty() : new LinePath(result);
  631.         }

  632.         /** Close the path by adding an end point equivalent to the path start point.
  633.          * @throws IllegalStateException if the path cannot be closed
  634.          */
  635.         private void closePath() {
  636.             final LineConvexSubset end = getEnd();

  637.             if (end != null) {
  638.                 if (startVertex != null && endVertex != null) {
  639.                     if (!endVertex.eq(startVertex, endVertexPrecision)) {
  640.                         appendInternal(Lines.segmentFromPoints(endVertex, startVertex, endVertexPrecision));
  641.                     }
  642.                 } else {
  643.                     throw new IllegalStateException("Unable to close line path: line path is infinite");
  644.                 }
  645.             }
  646.         }

  647.         /** Validate that the given line subsets  are connected, meaning that the end vertex of {@code previous}
  648.          * is equivalent to the start vertex of {@code next}. The line subsets are considered valid if either
  649.          * line subset is null.
  650.          * @param previous previous line subset
  651.          * @param next next line subset
  652.          * @throws IllegalStateException if previous and next are not null and the end vertex of previous
  653.          *      is not equivalent the start vertex of next
  654.          */
  655.         private void validateConnected(final LineConvexSubset previous, final LineConvexSubset next) {
  656.             if (previous != null && next != null) {
  657.                 final Vector2D nextStartVertex = next.getStartPoint();
  658.                 final Vector2D previousEndVertex = previous.getEndPoint();
  659.                 final Precision.DoubleEquivalence previousPrecision = previous.getPrecision();

  660.                 if (nextStartVertex == null || previousEndVertex == null ||
  661.                         !(nextStartVertex.eq(previousEndVertex, previousPrecision))) {

  662.                     throw new IllegalStateException(
  663.                             MessageFormat.format("Path line subsets are not connected: previous= {0}, next= {1}",
  664.                                     previous, next));
  665.                 }
  666.             }
  667.         }

  668.         /** Get the precision context used when adding raw vertices to the path. An exception is thrown
  669.          * if no precision has been specified.
  670.          * @return the precision context used when creating working with raw vertices
  671.          * @throws IllegalStateException if no precision context is configured
  672.          */
  673.         private Precision.DoubleEquivalence getAddVertexPrecision() {
  674.             if (precision == null) {
  675.                 throw new IllegalStateException("Unable to create line segment: no vertex precision specified");
  676.             }

  677.             return precision;
  678.         }

  679.         /** Append the given, validated line subsets to the path.
  680.          * @param subset validated line subset to append
  681.          */
  682.         private void appendInternal(final LineConvexSubset subset) {
  683.             if (appended == null) {
  684.                 appended = new ArrayList<>();
  685.             }

  686.             if (appended.isEmpty() &&
  687.                     (prepended == null || prepended.isEmpty())) {
  688.                 startVertex = subset.getStartPoint();
  689.             }

  690.             endVertex = subset.getEndPoint();
  691.             endVertexPrecision = subset.getPrecision();

  692.             appended.add(subset);
  693.         }

  694.         /** Prepend the given, validated line subset to the path.
  695.          * @param subset validated line subset to prepend
  696.          */
  697.         private void prependInternal(final LineConvexSubset subset) {
  698.             if (prepended == null) {
  699.                 prepended = new ArrayList<>();
  700.             }

  701.             startVertex = subset.getStartPoint();

  702.             if (prepended.isEmpty() &&
  703.                     (appended == null || appended.isEmpty())) {
  704.                 endVertex = subset.getEndPoint();
  705.                 endVertexPrecision = subset.getPrecision();
  706.             }

  707.             prepended.add(subset);
  708.         }

  709.         /** Get the first element in the list or null if the list is null
  710.          * or empty.
  711.          * @param list the list to return the first item from
  712.          * @return the first item from the given list or null if it does not exist
  713.          */
  714.         private LineConvexSubset getFirst(final List<? extends LineConvexSubset> list) {
  715.             if (list != null && !list.isEmpty()) {
  716.                 return list.get(0);
  717.             }
  718.             return null;
  719.         }

  720.         /** Get the last element in the list or null if the list is null
  721.          * or empty.
  722.          * @param list the list to return the last item from
  723.          * @return the last item from the given list or null if it does not exist
  724.          */
  725.         private LineConvexSubset getLast(final List<? extends LineConvexSubset> list) {
  726.             if (list != null && !list.isEmpty()) {
  727.                 return list.get(list.size() - 1);
  728.             }
  729.             return null;
  730.         }
  731.     }

  732.     /** Internal class returned when a line path is simplified to remove unnecessary line subset divisions.
  733.      * The {@link #simplify()} method on this class simply returns the same instance.
  734.      */
  735.     private static final class SimplifiedLinePath extends LinePath {
  736.         /** Create a new instance containing the given line subsets. No validation is
  737.          * performed on the inputs. Caller must ensure that the given line subsets represent
  738.          * a valid, simplified path.
  739.          * @param elements line subsets comprising the path
  740.          */
  741.         private SimplifiedLinePath(final List<LineConvexSubset> elements) {
  742.             super(elements);
  743.         }

  744.         /** {@inheritDoc} */
  745.         @Override
  746.         public SimplifiedLinePath simplify() {
  747.             // already simplified
  748.             return this;
  749.         }
  750.     }
  751. }