package com.revolsys.geometry.model.segment; import com.revolsys.geometry.algorithm.CGAlgorithms; import com.revolsys.geometry.algorithm.CGAlgorithmsDD; import com.revolsys.geometry.algorithm.HCoordinate; import com.revolsys.geometry.algorithm.NotRepresentableException; import com.revolsys.geometry.algorithm.RobustLineIntersector; import com.revolsys.geometry.cs.projection.ProjectionFactory; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.ClockDirection; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Side; import com.revolsys.geometry.model.coordinates.CoordinatesUtil; import com.revolsys.geometry.model.coordinates.LineSegmentUtil; import com.revolsys.geometry.model.coordinates.list.CoordinatesListUtil; import com.revolsys.geometry.util.BoundingBoxUtil; import com.revolsys.math.Angle; import com.revolsys.util.MathUtil; import com.revolsys.util.Property; import com.revolsys.util.number.Doubles; public interface LineSegment extends LineString { public static int addEndPointIntersection(final double[] coordinates, final int intersectionCount, final int axisCount, final LineSegment segment1, final int vertexIndex1, final LineSegment segment2, final int vertexIndex2) { final double x = segment1.getX(vertexIndex1); final double y = segment1.getY(vertexIndex1); if (!CoordinatesListUtil.containsXy(coordinates, intersectionCount, axisCount, x, y)) { coordinates[intersectionCount * axisCount] = x; coordinates[intersectionCount * axisCount + 1] = y; for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) { double value = segment1.getCoordinate(vertexIndex1, axisIndex); if (Double.isNaN(value)) { value = segment2.getCoordinate(vertexIndex2, axisIndex); } coordinates[intersectionCount * axisCount + axisIndex] = value; } return intersectionCount + 1; } return intersectionCount; } public static int addEndPointIntersectionProjected(final double[] coordinates, final int intersectionCount, final int axisCount, final LineSegment segment1, final int vertexIndex1, final LineSegment segment2, final double projectionFactor) { final double x = segment1.getX(vertexIndex1); final double y = segment1.getY(vertexIndex1); if (!CoordinatesListUtil.containsXy(coordinates, intersectionCount, axisCount, x, y)) { coordinates[intersectionCount * axisCount] = x; coordinates[intersectionCount * axisCount + 1] = y; for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) { double value = segment1.getCoordinate(vertexIndex1, axisIndex); if (Double.isNaN(value)) { value = segment2.projectCoordinate(axisIndex, projectionFactor); } coordinates[intersectionCount * axisCount + axisIndex] = value; } } return intersectionCount + 1; } public static int addPointIntersection(final double[] coordinates, final int intersectionCount, final int axisCount, final LineSegment segment1, final int vertexIndex, final LineSegment segment2) { final double x = segment1.getCoordinate(vertexIndex, 0); final double y = segment1.getCoordinate(vertexIndex, 1); if (segment2.equalsVertex(0, x, y)) { return addEndPointIntersection(coordinates, intersectionCount, axisCount, segment1, vertexIndex, segment2, 0); } else if (segment2.equalsVertex(1, x, y)) { return addEndPointIntersection(coordinates, intersectionCount, axisCount, segment1, vertexIndex, segment2, 1); } else { final double distance = segment2.distance(x, y); final double maxDistance = segment1.getGeometryFactory().getResolution(0); if (distance < maxDistance) { final double x1 = segment2.getX(0); final double y1 = segment2.getY(0); final double x2 = segment2.getX(1); final double y2 = segment2.getY(1); final double projectionFactor = LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x, y); if (projectionFactor >= 0.0 && projectionFactor <= 1.0) { return addEndPointIntersectionProjected(coordinates, intersectionCount, axisCount, segment1, vertexIndex, segment2, projectionFactor); } } } return intersectionCount; } /** * Computes the angle that the vector defined by this segment * makes with the X-axis. * The angle will be in the range [ -PI, PI ] radians. * * @return the angle this segment makes with the X-axis (in radians) */ default double angle() { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return Angle.angle2d(x1, y1, x2, y2); } /** * Computes the closest point on this line segment to another point. * @param point the point to find the closest point to * @return a Point which is the closest point on the line segment to the point p */ default Point closestPoint(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return LineSegmentUtil.closestPoint(x1, y1, x2, y2, x, y); } /** * Computes the closest point on this line segment to another point. * @param point the point to find the closest point to * @return a Point which is the closest point on the line segment to the point p */ default Point closestPoint(final Point point) { final double x = point.getX(); final double y = point.getY(); final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return LineSegmentUtil.closestPoint(x1, y1, x2, y2, x, y); } /** * Computes the closest points on two line segments. * * @param line the segment to find the closest point to * @return a pair of Point which are the closest points on the line segments */ default Point[] closestPoints(final LineSegment line) { // test for intersection final Point intPt = intersection(line); if (intPt != null) { return new Point[] { intPt, intPt }; } /** * if no intersection closest pair contains at least one endpoint. Test each * endpoint in turn. */ final Point[] closestPt = new Point[2]; double minDistance = Double.MAX_VALUE; double dist; final Point lineStart = line.getPoint(0); final Point lineEnd = line.getPoint(1); final Point close00 = closestPoint(lineStart); minDistance = close00.distancePoint(lineStart); closestPt[0] = close00; closestPt[1] = lineStart; final Point close01 = closestPoint(lineEnd); dist = close01.distancePoint(lineEnd); if (dist < minDistance) { minDistance = dist; closestPt[0] = close01; closestPt[1] = lineEnd; } final Point start = getPoint(0); final Point close10 = line.closestPoint(start); dist = close10.distancePoint(start); if (dist < minDistance) { minDistance = dist; closestPt[0] = start; closestPt[1] = close10; } final Point close11 = line.closestPoint(start); dist = close11.distancePoint(start); if (dist < minDistance) { minDistance = dist; closestPt[0] = start; closestPt[1] = close11; } return closestPt; } default int compareTo(final int index, final LineSegment lineSegment) { final double x1 = getX(index); final double y1 = getY(index); final double x2 = lineSegment.getX(index); final double y2 = lineSegment.getY(index); return CoordinatesUtil.compare(x1, y1, x2, y2); } /** * Compares this object with the specified object for order. * Uses the standard lexicographic ordering for the points in the LineSegmentDouble. * *@param o the <code>LineSegmentDouble</code> with which this <code>LineSegmentDouble</code> * is being compared *@return a negative integer, zero, or a positive integer as this <code>LineSegmentDouble</code> * is less than, equal to, or greater than the specified <code>LineSegmentDouble</code> */ @Override default int compareTo(final Object other) { if (other instanceof LineSegment) { final LineSegment segment = (LineSegment)other; int compare = compareTo(0, segment); if (compare == 0) { compare = compareTo(1, segment); } return compare; } else { return LineString.super.compareTo(other); } } @Override @SuppressWarnings("unchecked") default <V extends Geometry> V convertGeometry(final GeometryFactory geometryFactory) { final GeometryFactory factory = getGeometryFactory(); if (geometryFactory == factory) { return (V)this; } else { final Point point1 = ProjectionFactory.convert(getPoint(0), factory, geometryFactory); final Point point2 = ProjectionFactory.convert(getPoint(1), factory, geometryFactory); return (V)new LineSegmentDoubleGF(geometryFactory, point1, point2); } } @Override default double distance(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); if (x1 == x2 && y1 == y2) { return MathUtil.distance(x, y, x1, y1); } else { final double dxx1 = x - x1; final double dx2x1 = x2 - x1; final double dyy1 = y - y1; final double dy2y1 = y2 - y1; final double d2x1sq = dx2x1 * dx2x1; final double dy2y1sq = dy2y1 * dy2y1; final double r = (dxx1 * dx2x1 + dyy1 * dy2y1) / (d2x1sq + dy2y1sq); if (r <= 0.0) { return MathUtil.distance(x, y, x1, y1); } else if (r >= 1.0) { return MathUtil.distance(x, y, x2, y2); } else { final double dy1y = y1 - y; final double dx1x = x1 - x; final double s = (dy1y * dx2x1 - dx1x * dy2y1) / (d2x1sq + dy2y1sq); return Math.abs(s) * Math.sqrt(d2x1sq + dy2y1sq); } } } /** * Computes the distance between this line segment and a given point. * * @return the distance from this segment to the given point */ @Override default double distance(final double x, final double y, final double terminateDistance) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y); } default double distance(final double x1, final double y1, final double x2, final double y2) { final double line1x1 = getX(0); final double line1y1 = getY(0); final double line1x2 = getX(1); final double line1y2 = getY(1); return LineSegmentUtil.distanceLineLine(line1x1, line1y1, line1x2, line1y2, x1, y1, x2, y2); } default double distance(final LineSegment line) { final double[] coordinates = new double[2]; final GeometryFactory geometryFactory = getGeometryFactory(); line.convertVertexCoordinates2d(0, geometryFactory, coordinates); final double x1 = coordinates[X]; final double y1 = coordinates[Y]; line.convertVertexCoordinates2d(1, geometryFactory, coordinates); final double x2 = coordinates[X]; final double y2 = coordinates[Y]; return distance(x1, y1, x2, y2); } default double distanceAlong(final double x, final double y) { final double projectionFactor = projectionFactor(x, y); if (projectionFactor >= 0 && projectionFactor <= 1) { return getLength() * projectionFactor; } else { return 0; } } @Override default double distanceAlong(final Point point) { return distanceAlong(point.getX(), point.getY()); } /** * Computes the perpendicular distance between the (infinite) line defined * by this line segment and a point. * * @return the perpendicular distance between the defined line and the given point */ default double distancePerpendicular(final Point p) { final double x = p.getX(); final double y = p.getY(); final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return LineSegmentUtil.distancePointLinePerpendicular(x, y, x1, y1, x2, y2); } default boolean equals(final LineSegment segment) { if (isEmpty()) { return false; } else if (segment.isEmpty()) { return false; } else { if (equalsVertex(2, 0, segment, 0)) { if (equalsVertex(2, 1, segment, 1)) { return true; } } else if (equalsVertex(2, 0, segment, 1)) { if (equalsVertex(2, 1, segment, 0)) { return true; } } return false; } } /** * Returns <code>true</code> if <code>other</code> is * topologically equal to this LineSegment (e.g. irrespective * of orientation). * *@param other a <code>LineSegment</code> with which to do the comparison. *@return <code>true</code> if <code>other</code> is a <code>LineSegment</code> * with the same values for the x and y ordinates in forwards or reverse order. */ default boolean equalsTopo(final LineSegment other) { return equals(other); } default LineSegment extend(final double startDistance, final double endDistance) { final double angle = angle(); final Point c1 = CoordinatesUtil.offset(getPoint(0), angle, -startDistance); final Point c2 = CoordinatesUtil.offset(getPoint(1), angle, endDistance); return new LineSegmentDoubleGF(getGeometryFactory(), c1, c2); } @Override default int getAxisCount() { return 3; } default ClockDirection getClockDirection(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); final ClockDirection clockDirection = ClockDirection.directionLinePoint(x1, y1, x2, y2, x, y); return clockDirection; } default Point getCrossing(final Point point1, final Point point2, final BoundingBox boundingBox) { final GeometryFactory geometryFactory = getGeometryFactory(); Point intersection = null; final Polygon polygon = boundingBox.toPolygon(1); final LineString ring = polygon.getShell(); for (int i = 0; i < 4; i++) { final Point ringC1 = ring.getPoint(i); final Point ringC2 = ring.getPoint(i); final LineString currentIntersections = LineSegmentUtil.getIntersection(geometryFactory, point1, point2, ringC1, ringC2); if (currentIntersections.getVertexCount() == 1) { final Point currentIntersection = currentIntersections.getPoint(0); if (intersection == null) { intersection = currentIntersection; } else if (point1.distancePoint(currentIntersection) < point1.distancePoint(intersection)) { intersection = currentIntersection; } } } return intersection; } default double getElevation(final Point point) { return CoordinatesUtil.getElevation(point, getPoint(0), getPoint(1)); } default LineSegment getIntersection(BoundingBox boundingBox) { final GeometryFactory geometryFactory = getGeometryFactory(); boundingBox = boundingBox.convert(geometryFactory); final Point lineStart = getPoint(0); final Point lineEnd = getPoint(1); final boolean contains1 = boundingBox.covers(lineStart); final boolean contains2 = boundingBox.covers(lineEnd); if (contains1) { if (contains2) { return this; } else { final Point c1 = lineStart; final Point c2 = getCrossing(lineEnd, lineStart, boundingBox); return new LineSegmentDoubleGF(geometryFactory, c1, c2); } } else { if (contains2) { final Point c1 = getCrossing(lineStart, lineEnd, boundingBox); final Point c2 = lineEnd; return new LineSegmentDoubleGF(geometryFactory, c1, c2); } else { final Point c1 = getCrossing(lineStart, lineEnd, boundingBox); final Point c2 = getCrossing(lineEnd, lineStart, boundingBox); return new LineSegmentDoubleGF(geometryFactory, c1, c2); } } } default Geometry getIntersection(final LineSegment lineSegment2) { final GeometryFactory geometryFactory = getGeometryFactory(); lineSegment2.convertGeometry(geometryFactory); final double line1x1 = getX(0); final double line1y1 = getY(0); final double line1x2 = getX(1); final double line1y2 = getY(1); final double line2x1 = lineSegment2.getX(0); final double line2y1 = lineSegment2.getY(0); final double line2x2 = lineSegment2.getX(1); final double line2y2 = lineSegment2.getY(1); if (BoundingBoxUtil.intersectsMinMax(line1x1, line1y1, line1x2, line1y2, line2x1, line2y1, line2x2, line2y2)) { int intersectionCount = 0; final int axisCount = geometryFactory.getAxisCount(); final double[] coordinates = new double[2 * axisCount]; for (int vertexIndex = 0; vertexIndex < 2; vertexIndex++) { intersectionCount = addPointIntersection(coordinates, intersectionCount, axisCount, this, vertexIndex, lineSegment2); } for (int vertexIndex = 0; vertexIndex < 2; vertexIndex++) { intersectionCount = addPointIntersection(coordinates, intersectionCount, axisCount, lineSegment2, vertexIndex, this); } if (intersectionCount == 0) { final int Pq1 = CoordinatesListUtil.orientationIndex(line1x1, line1y1, line1x2, line1y2, line2x1, line2y1); final int Pq2 = CoordinatesListUtil.orientationIndex(line1x1, line1y1, line1x2, line1y2, line2x2, line2y2); if (!(Pq1 > 0 && Pq2 > 0 || Pq1 < 0 && Pq2 < 0)) { final int Qp1 = CoordinatesListUtil.orientationIndex(line2x1, line2y1, line2x2, line2y2, line1x1, line1y1); final int Qp2 = CoordinatesListUtil.orientationIndex(line2x1, line2y1, line2x2, line2y2, line1x2, line1y2); if (!(Qp1 > 0 && Qp2 > 0 || Qp1 < 0 && Qp2 < 0)) { final double detLine1StartLine1End = LineSegmentUtil.det(line1x1, line1y1, line1x2, line1y2); final double detLine2StartLine2End = LineSegmentUtil.det(line2x1, line2y1, line2x2, line2y2); double x = LineSegmentUtil.det(detLine1StartLine1End, line1x1 - line1x2, detLine2StartLine2End, line2x1 - line2x2) / LineSegmentUtil.det(line1x1 - line1x2, line1y1 - line1y2, line2x1 - line2x2, line2y1 - line2y2); x = geometryFactory.makePrecise(0, x); double y = LineSegmentUtil.det(detLine1StartLine1End, line1y1 - line1y2, detLine2StartLine2End, line2y1 - line2y2) / LineSegmentUtil.det(line1x1 - line1x2, line1y1 - line1y2, line2x1 - line2x2, line2y1 - line2y2); y = geometryFactory.makePrecise(1, y); coordinates[0] = x; coordinates[1] = y; boolean hasNaN = false; double projectionFactor = projectionFactor(x, y); for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) { final double value = projectCoordinate(axisIndex, projectionFactor); coordinates[axisIndex] = value; hasNaN |= Double.isNaN(value); } if (hasNaN) { projectionFactor = lineSegment2.projectionFactor(x, y); for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) { double value = coordinates[axisIndex]; if (Double.isNaN(value)) { value = lineSegment2.projectCoordinate(axisIndex, projectionFactor); coordinates[axisIndex] = value; hasNaN |= Double.isNaN(value); } } } return geometryFactory.point(coordinates); } } } else if (intersectionCount == 1) { return geometryFactory.point(coordinates); } else if (intersectionCount == 2) { final double distance1 = MathUtil.distance(line1x1, line1y1, coordinates[0], coordinates[1]); final double distance2 = MathUtil.distance(line1x1, line1y1, coordinates[axisCount], coordinates[axisCount + 1]); if (distance1 > distance2) { CoordinatesListUtil.switchCoordinates(coordinates, axisCount, 0, 1); } return new LineSegmentDoubleGF(geometryFactory, axisCount, coordinates); } } return null; } /** * Computes the length of the line segment. * @return the length of the line segment */ @Override default double getLength() { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return MathUtil.distance(x1, y1, x2, y2); } default double getOrientaton() { if (isEmpty()) { return 0; } else { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); final double angle = Angle.angleDegrees(x1, y1, x2, y2); if (Double.isNaN(angle)) { return 0; } else { return angle; } } } default Point getP0() { return getPoint(0); } default Point getP1() { return getPoint(1); } @Override default Side getSide(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return Side.getSide(x1, y1, x2, y2, x, y); } @Override default int getVertexCount() { return 2; } /** * Computes an intersection point between two line segments, if there is one. * There may be 0, 1 or many intersection points between two segments. * If there are 0, null is returned. If there is 1 or more, * exactly one of them is returned * (chosen at the discretion of the algorithm). * If more information is required about the details of the intersection, * the {@link RobustLineIntersector} class should be used. * * @param line a line segment * @return an intersection point, or <code>null</code> if there is none * * @see RobustLineIntersector */ default Point intersection(final LineSegment line) { final Geometry intersection = getIntersection(line); if (intersection == null) { return null; } else { return intersection.getPoint(); } } default boolean intersects(final Point point, final double maxDistance) { return LineSegmentUtil.isPointOnLine(getPoint(0), getPoint(1), point, maxDistance); } @Override default boolean isEmpty() { return Double.isNaN(getCoordinate(0, 1)); } default boolean isEndPoint(final double x, final double y) { if (equalsVertex(0, x, y)) { return true; } else if (equalsVertex(1, x, y)) { return true; } else { return false; } } default boolean isEndPoint(final Point point) { if (point == null) { return false; } else { final double x = point.getX(); final double y = point.getY(); return isEndPoint(x, y); } } /** * Tests whether the segment is horizontal. * * @return <code>true</code> if the segment is horizontal */ default boolean isHorizontal() { return getY(0) == getY(1); } default boolean isLeftOf(final double x, final double y) { final ClockDirection clockDirection = getClockDirection(x, y); return clockDirection.isCounterClockwise(); } default boolean isPerpendicularTo(Point point) { if (Property.hasValuesAll(point, this)) { final GeometryFactory geometryFactory = getGeometryFactory(); point = point.convertGeometry(geometryFactory, 2); final double x = point.getX(); final double y = point.getY(); final double projectionFactor = projectionFactor(x, y); if (projectionFactor >= 0.0 && projectionFactor <= 1.0) { return true; } } return false; } default boolean isPointOnLineMiddle(final double x, final double y, final double maxDistance) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); if (Doubles.equal(x, x1) && Doubles.equal(y, y1)) { return false; } else if (Doubles.equal(x, x2) && Doubles.equal(y, y2)) { return false; } else { final double distance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y); if (distance < maxDistance) { final double projectionFactor = LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x, y); if (projectionFactor >= 0.0 && projectionFactor <= 1.0) { return true; } } return false; } } default boolean isPointOnLineMiddle(Point point, final double maxDistance) { if (point == null || point.isEmpty()) { return false; } else { final GeometryFactory geometryFactory = getGeometryFactory(); point = point.convertGeometry(geometryFactory, 2); final double x = point.getX(); final double y = point.getY(); return isPointOnLineMiddle(x, y, maxDistance); } } default boolean isRightOf(final double x, final double y) { final ClockDirection clockDirection = getClockDirection(x, y); return clockDirection.isClockwise(); } /** * Tests whether the segment is vertical. * * @return <code>true</code> if the segment is vertical */ default boolean isVertical() { return getX(0) == getX(1); } default boolean isWithinDistance(final Point point, final double distance) { return distancePoint(point) <= distance; } default boolean isZeroLength() { return equalsVertex2d(0, 1); } /** * Computes the intersection point of the lines of infinite extent defined * by two line segments (if there is one). * There may be 0, 1 or an infinite number of intersection points * between two lines. * If there is a unique intersection point, it is returned. * Otherwise, <tt>null</tt> is returned. * If more information is required about the details of the intersection, * the {@link RobustLineIntersector} class should be used. * * @param line a line segment defining an straight line with infinite extent * @return an intersection point, * or <code>null</code> if there is no point of intersection * or an infinite number of intersection points * * @see RobustLineIntersector */ default Point lineIntersection(final LineSegment line) { try { final Point intPt = HCoordinate.intersection(getP0(), getP1(), line.getP0(), line.getP1()); return intPt; } catch (final NotRepresentableException ex) { // eat this exception, and return null; } return null; } /** * Computes the midpoint of the segment * * @return the midpoint of the segment */ default Point midPoint() { return LineSegmentUtil.midPoint(getGeometryFactory(), getPoint(0), getPoint(1)); } default LineSegment newLineSegment(final int axisCount, final double... coordinates) { final GeometryFactory geometryFactory = getGeometryFactory(); return new LineSegmentDoubleGF(geometryFactory, axisCount, coordinates); } default Point newPoint(final double... coordinates) { final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.point(coordinates); } /** * Puts the line segment into a normalized form. * This is useful for using line segments in maps and indexes when * topological equality rather than exact equality is desired. * A segment in normalized form has the first point smaller * than the second (according to the standard ordering on {@link Coordinates}). */ @Override default LineSegment normalize() { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); if (CoordinatesUtil.compare(x1, y1, x2, y2) < 0) { return reverse(); } else { return this; } } default int orientationIndex(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return LineSegmentUtil.orientationIndex(x1, y1, x2, y2, x, y); } /** * Determines the orientation of a LineSegment relative to this segment. * The concept of orientation is specified as follows: * Given two line segments A and L, * <ul * <li>A is to the left of a segment L if A lies wholly in the * closed half-plane lying to the left of L * <li>A is to the right of a segment L if A lies wholly in the * closed half-plane lying to the right of L * <li>otherwise, A has indeterminate orientation relative to L. This * happens if A is collinear with L or if A crosses the line determined by L. * </ul> * * @param seg the LineSegment to compare * * @return 1 if <code>seg</code> is to the left of this segment * @return -1 if <code>seg</code> is to the right of this segment * @return 0 if <code>seg</code> has indeterminate orientation relative to this segment */ default int orientationIndex(final LineSegment seg) { final int orient0 = CoordinatesUtil.orientationIndex(getPoint(0), getPoint(1), seg.getPoint(0)); final int orient1 = CoordinatesUtil.orientationIndex(getPoint(0), getPoint(1), seg.getPoint(1)); // this handles the case where the points are L or collinear if (orient0 >= 0 && orient1 >= 0) { return Math.max(orient0, orient1); } // this handles the case where the points are R or collinear if (orient0 <= 0 && orient1 <= 0) { return Math.max(orient0, orient1); } // points lie on opposite sides ==> indeterminate orientation return 0; } /** * Determines the orientation index of a {@link Coordinates} relative to this segment. * The orientation index is as defined in {@link CGAlgorithms#computeOrientation}. * * @param p the coordinate to compare * * @return 1 (LEFT) if <code>p</code> is to the left of this segment * @return -1 (RIGHT) if <code>p</code> is to the right of this segment * @return 0 (COLLINEAR) if <code>p</code> is collinear with this segment * * @see CGAlgorithms#computeOrientation(Coordinate, Coordinate, Coordinate) */ default int orientationIndex(final Point p) { /** * MD - 9 Aug 2010 It seems that the basic algorithm is slightly orientation * dependent, when computing the orientation of a point very close to a * line. This is possibly due to the arithmetic in the translation to the * origin. * * For instance, the following situation produces identical results in spite * of the inverse orientation of the line segment: * * Point p0 = new PointDouble((double)219.3649559090992, 140.84159161824724); * Point p1 = new PointDouble((double)168.9018919682399, -5.713787599646864); * * Point p = new PointDouble((double)186.80814046338352, 46.28973405831556); int * orient = orientationIndex(p0, p1, p); int orientInv = * orientationIndex(p1, p0, p); * * A way to force consistent results is to normalize the orientation of the * vector using the following code. However, this may make the results of * orientationIndex inconsistent through the triangle of points, so it's not * clear this is an appropriate patch. * */ return CGAlgorithmsDD.orientationIndex(getP0(), getP1(), p); } /** * Computes the {@link Coordinates} that lies a given * fraction along the line defined by this segment. * A fraction of <code>0.0</code> returns the start point of the segment; * a fraction of <code>1.0</code> returns the end point of the segment. * If the fraction is < 0.0 or > 1.0 the point returned * will lie before the start or beyond the end of the segment. * * @param projectionFactor the fraction of the segment length along the line * @return the point at that distance */ default Point pointAlong(final double projectionFactor) { final GeometryFactory geometryFactory = getGeometryFactory(); final int axisCount = getAxisCount(); final double[] coordinates = new double[axisCount]; for (int i = 0; i < axisCount; i++) { final double value1 = getCoordinate(0, i); final double value2 = getCoordinate(1, i); final double delta = value2 - value1; double newValue = value1 + delta * projectionFactor; newValue = geometryFactory.makePrecise(i, newValue); coordinates[i] = newValue; } return newPoint(coordinates); } /** * Computes the {@link Coordinates} that lies a given * fraction along the line defined by this segment and offset from * the segment by a given distance. * A fraction of <code>0.0</code> offsets from the start point of the segment; * a fraction of <code>1.0</code> offsets from the end point of the segment. * The computed point is offset to the left of the line if the offset distance is * positive, to the right if negative. * * @param segmentLengthFraction the fraction of the segment length along the line * @param offsetDistance the distance the point is offset from the segment * (positive is to the left, negative is to the right) * @return the point at that distance and offset * * @throws IllegalStateException if the segment has zero length */ default Point pointAlongOffset(final double segmentLengthFraction, final double offsetDistance) { final double x1 = getX(0); final double x2 = getX(1); final double dx = x2 - x1; final double y1 = getY(0); final double y2 = getY(1); final double dy = y2 - y1; // the point on the segment line double x = x1 + segmentLengthFraction * dx; double y = y1 + segmentLengthFraction * dy; final double len = Math.sqrt(dx * dx + dy * dy); if (offsetDistance != 0.0) { if (len <= 0.0) { throw new IllegalStateException("Cannot compute offset from zero-length line segment"); } double ux = 0.0; double uy = 0.0; // u is the vector that is the length of the offset, in the direction of // the segment ux = offsetDistance * dx / len; uy = offsetDistance * dy / len; // the offset point is the seg point plus the offset vector rotated 90 // degrees CCW x = x - uy; y = y + ux; } return newPoint(x, y); } default Point project(final double x, final double y) { final double projectionFactor = projectionFactor(x, y); return pointAlong(projectionFactor); } /** * Project a line segment onto this line segment and return the resulting * line segment. The returned line segment will be a subset of * the target line line segment. This subset may be null, if * the segments are oriented in such a way that there is no projection. * <p> * Note that the returned line may have zero length (i.e. the same endpoints). * This can happen for instance if the lines are perpendicular to one another. * * @param seg the line segment to project * @return the projected line segment, or <code>null</code> if there is no overlap */ default LineSegment project(final LineSegment seg) { final double x1 = seg.getX(0); final double y1 = seg.getY(0); final double x2 = seg.getX(1); final double y2 = seg.getY(1); final double pf0 = projectionFactor(x1, y1); final double pf1 = projectionFactor(x2, y2); // check if segment projects at all if (pf0 >= 1.0 && pf1 >= 1.0) { return null; } else if (pf0 <= 0.0 && pf1 <= 0.0) { return null; } else { double newX1; double newY1; if (pf0 <= 0.0) { newX1 = getX(0); newY1 = getY(0); } else if (pf0 >= 1.0) { newX1 = getX(1); newY1 = getY(1); } else { final Point newPoint = pointAlong(pf0); newX1 = newPoint.getX(); newY1 = newPoint.getY(); } double newX2; double newY2; if (pf1 <= 0.0) { newX2 = getX(0); newY2 = getY(0); } else if (pf1 > 1.0) { newX2 = getX(1); newY2 = getY(1); } else { final Point newPoint = pointAlong(pf1); newX2 = newPoint.getX(); newY2 = newPoint.getY(); } return newLineSegment(2, newX1, newY1, newX2, newY2); } } /** * Compute the projection of a point onto the line determined * by this line segment. * <p> * Note that the projected point * may lie outside the line segment. If this is the case, * the projection factor will lie outside the range [0.0, 1.0]. */ default Point project(final Point point) { final double projectionFactor = projectionFactor(point); return pointAlong(projectionFactor); } default double projectCoordinate(final int axisIndex, final double projectionFactor) { final double value1 = getCoordinate(0, axisIndex); final double value2 = getCoordinate(1, axisIndex); if (Double.isNaN(value1) || Double.isNaN(value2)) { return Double.NaN; } else { return value1 + (value2 - value1) * projectionFactor; } } /** * Computes the Projection Factor for the projection of the point p * onto this LineSegmentDouble. The Projection Factor is the constant r * by which the vector for this segment must be multiplied to * equal the vector for the projection of <tt>p<//t> on the line * defined by this segment. * <p> * The projection factor will lie in the range <tt>(-inf, +inf)</tt>, * or be <code>NaN</code> if the line segment has zero length.. * * @param x the point x coordinate to compute the factor for * @param y the point y coordinate to compute the factor for * @return the projection factor for the point */ default double projectionFactor(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); return LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x, y); } /** * Computes the Projection Factor for the projection of the point p * onto this LineSegmentDouble. The Projection Factor is the constant r * by which the vector for this segment must be multiplied to * equal the vector for the projection of <tt>p<//t> on the line * defined by this segment. * <p> * The projection factor will lie in the range <tt>(-inf, +inf)</tt>, * or be <code>NaN</code> if the line segment has zero length.. * * @param point the point to compute the factor for * @return the projection factor for the point */ default double projectionFactor(final Point point) { final double x = point.getX(); final double y = point.getY(); return projectionFactor(x, y); } /** * Reverses the direction of the line segment. */ @Override default LineSegment reverse() { final int axisCount = getAxisCount(); final double[] coordinates = new double[axisCount * 2]; for (int vertexIndex = 0; vertexIndex < 2; vertexIndex++) { for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) { coordinates[vertexIndex * axisCount + axisIndex] = getCoordinate(1 - vertexIndex, axisIndex); } } return newLineSegment(axisCount, coordinates); } /** * Computes the fraction of distance (in <tt>[0.0, 1.0]</tt>) * that the projection of a point occurs along this line segment. * If the point is beyond either ends of the line segment, * the closest fractional value (<tt>0.0</tt> or <tt>1.0</tt>) is returned. * <p> * Essentially, this is the {@link #projectionFactor} clamped to * the range <tt>[0.0, 1.0]</tt>. * If the segment has zero length, 1.0 is returned. * * @param inputPt the point * @return the fraction along the line segment the projection of the point occurs */ default double segmentFraction(final Point inputPt) { double segFrac = projectionFactor(inputPt); if (segFrac < 0.0) { segFrac = 0.0; } else if (segFrac > 1.0 || Double.isNaN(segFrac)) { segFrac = 1.0; } return segFrac; } default boolean touchesEnd(final LineSegment closestSegment) { if (isEndPoint(closestSegment.getPoint(0))) { return true; } else if (isEndPoint(closestSegment.getPoint(1))) { return true; } return false; } }