public static Set<String> edgeTypeNames(final Node<?> node) {
   final Set<String> typePaths = new HashSet<>();
   for (final Edge<?> edge : node.getEdges()) {
     final String typePath = edge.getTypeName();
     typePaths.add(typePath);
   }
   return typePaths;
 }
 public static Set<RecordDefinition> edgeRecordDefinitions(final Node<?> node) {
   final Set<RecordDefinition> recordDefinitions = new HashSet<>();
   for (final Edge<?> edge : node.getEdges()) {
     final Object object = edge.getObject();
     if (object instanceof Record) {
       final Record record = (Record) object;
       final RecordDefinition recordDefinition = record.getRecordDefinition();
       recordDefinitions.add(recordDefinition);
     }
   }
   return recordDefinitions;
 }
 public static Set<Double> edgeAngles(final Node<?> node) {
   final Set<Double> angles = new TreeSet<>(new NumericComparator<Double>());
   for (final Edge<?> edge : node.getInEdges()) {
     final double toAngle = edge.getToAngle();
     angles.add(toAngle);
   }
   for (final Edge<?> edge : node.getOutEdges()) {
     final double fromAngle = edge.getFromAngle();
     angles.add(fromAngle);
   }
   return angles;
 }
 public static <T> Map<String, List<Edge<T>>> edgesByType(final Node<T> node) {
   final Map<String, List<Edge<T>>> edgesByType = new HashMap<>();
   for (final Edge<T> edge : node.getEdges()) {
     final String typePath = edge.getTypeName();
     List<Edge<T>> typeEdges = edgesByType.get(typePath);
     if (typeEdges == null) {
       typeEdges = new ArrayList<>();
       edgesByType.put(typePath, typeEdges);
     }
     typeEdges.add(edge);
   }
   return edgesByType;
 }
    public static <T> Map<String, Map<LineString, Set<Edge<T>>>> edgesByTypeNameAndLine(
        final Node<T> node) {
      final List<Edge<T>> edges = node.getEdges();
      final Map<String, Map<LineString, Set<Edge<T>>>> typeLineEdgeMap = new HashMap<>();
      for (final Edge<T> edge : new HashSet<>(edges)) {
        final String typePath = edge.getTypeName();
        Map<LineString, Set<Edge<T>>> lineEdgeMap = typeLineEdgeMap.get(typePath);
        if (lineEdgeMap == null) {
          lineEdgeMap = new HashMap<>();
          typeLineEdgeMap.put(typePath, lineEdgeMap);
        }

        Edge.addEdgeToEdgesByLine(node, lineEdgeMap, edge);
      }
      return typeLineEdgeMap;
    }
  /**
   * Check within a given tolerance that the LINESTRING defined by points2 is contained within the
   * points1.
   *
   * <p>The algorithm is as follows:
   *
   * <ol>
   *   <li>Find all coordinates from points2 that are within the tolerance from the line segments of
   *       points1.
   *   <li>Find all coordinates from points1 that are within the tolerance from the line segments of
   *       points2.
   *   <li>Split all the line sgements of points1 that were matched in step 1.
   *   <li>Split all the line sgements of points2 that were matched in step 2.
   *   <li>Line is contained if all line segments from point2 have matching lines in points1.
   * </ol>
   *
   * @param points1
   * @param points2
   * @param tolerance
   * @return
   */
  public static boolean containsWithinTolerance(
      final LineString points1, final LineString points2, final double tolerance) {

    final LineStringGraph graph1 = new LineStringGraph(points1);
    final LineStringGraph graph2 = new LineStringGraph(points2);
    graph1.forEachNode((node) -> movePointsWithinTolerance(null, graph2, tolerance, node));
    graph1.forEachNode((node) -> movePointsWithinTolerance(null, graph1, tolerance, node));

    final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge1 =
        graph1.getPointsOnEdges(graph2, tolerance);
    final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge2 =
        graph2.getPointsOnEdges(graph1, tolerance);
    graph1.splitEdges(pointsOnEdge1);
    graph2.splitEdges(pointsOnEdge2);
    for (final Edge<LineSegment> edge : graph2.getEdges()) {
      final Node<LineSegment> fromNode = edge.getFromNode();
      final Node<LineSegment> toNode = edge.getToNode();
      if (!graph1.hasEdgeBetween(fromNode, toNode)) {
        return false;
      }
    }
    return true;
  }
 public static <T> Map<LineString, Map<String, Set<Edge<T>>>> edgesByLineAndTypeName(
     final Node<T> node) {
   final List<Edge<T>> edges = node.getEdges();
   final Map<LineString, Map<String, Set<Edge<T>>>> lineEdgeMap = new HashMap<>();
   for (final Edge<T> edge : new HashSet<>(edges)) {
     LineString line = edge.getLine();
     Map<String, Set<Edge<T>>> edgesByType = edgesByTypeForLine(lineEdgeMap, line);
     if (edgesByType == null) {
       edgesByType = new HashMap<>();
       if (edge.getEnd(node).isTo()) {
         line = line.reverse();
       }
       lineEdgeMap.put(line, edgesByType);
     }
     Set<Edge<T>> typeEdges = edgesByType.get(edge.getTypeName());
     if (typeEdges == null) {
       typeEdges = new HashSet<>();
       final String typePath = edge.getTypeName();
       edgesByType.put(typePath, typeEdges);
     }
     typeEdges.add(edge);
   }
   return lineEdgeMap;
 }
 public static Map<String, Set<Double>> edgeAnglesByType(final Node<?> node) {
   final Map<String, Set<Double>> anglesByType = new HashMap<>();
   for (final Edge<?> edge : node.getInEdges()) {
     final String typePath = edge.getTypeName();
     final double toAngle = edge.getToAngle();
     final Set<Double> angles = getAnglesForType(anglesByType, typePath);
     angles.add(toAngle);
   }
   for (final Edge<?> edge : node.getOutEdges()) {
     final String typePath = edge.getTypeName();
     final double fromAngle = edge.getFromAngle();
     final Set<Double> angles = getAnglesForType(anglesByType, typePath);
     angles.add(fromAngle);
   }
   return anglesByType;
 }
  public static List<LineString> intersection(
      final GeometryFactory geometryFactory,
      final LineString points1,
      final LineString points2,
      final double maxDistance) {

    final LineStringGraph graph1 = new LineStringGraph(points1);
    graph1.setPrecisionModel(geometryFactory);
    final LineStringGraph graph2 = new LineStringGraph(points2);
    graph2.setPrecisionModel(geometryFactory);
    final Map<Point, Point> movedNodes = new HashMap<>();
    graph1.forEachNode((node) -> movePointsWithinTolerance(movedNodes, graph2, maxDistance, node));
    graph2.forEachNode((node) -> movePointsWithinTolerance(movedNodes, graph1, maxDistance, node));

    final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge1 =
        graph1.getPointsOnEdges(graph2, maxDistance);
    final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge2 =
        graph2.getPointsOnEdges(graph1, maxDistance);
    graph1.splitEdges(pointsOnEdge1);
    graph2.splitEdges(pointsOnEdge2);
    Point startPoint = points1.getPoint(0);
    if (movedNodes.containsKey(startPoint)) {
      startPoint = movedNodes.get(startPoint);
    }
    Point endPoint = points1.getPoint(points1.getVertexCount() - 1);
    if (movedNodes.containsKey(endPoint)) {
      endPoint = movedNodes.get(endPoint);
    }
    final List<LineString> intersections = new ArrayList<>();
    final List<Point> currentCoordinates = new ArrayList<>();
    Node<LineSegment> previousNode = graph1.getNode(startPoint);
    do {
      final List<Edge<LineSegment>> outEdges = previousNode.getOutEdges();
      if (outEdges.isEmpty()) {
        previousNode = null;
      } else if (outEdges.size() > 1) {
        throw new IllegalArgumentException("Cannot handle overlaps\n" + points1 + "\n " + points2);
      } else {
        final Edge<LineSegment> edge = outEdges.get(0);
        final LineSegment line = edge.getObject();
        final Node<LineSegment> nextNode = edge.getToNode();
        if (graph2.hasEdgeBetween(previousNode, nextNode)) {
          if (currentCoordinates.size() == 0) {
            currentCoordinates.add(line.getPoint(0));
          }
          currentCoordinates.add(line.getPoint(1));
        } else {
          if (currentCoordinates.size() > 0) {
            final LineString points =
                new LineStringDouble(points1.getAxisCount(), currentCoordinates);
            intersections.add(points);
            currentCoordinates.clear();
          }
        }
        previousNode = nextNode;
      }

    } while (previousNode != null && !endPoint.equals(2, startPoint));
    if (currentCoordinates.size() > 0) {
      final LineString points = new LineStringDouble(points1.getAxisCount(), currentCoordinates);
      intersections.add(points);
    }
    return intersections;
  }