package org.djutils.draw.line;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Iterator;
import org.djutils.base.AngleUtil;
import org.djutils.draw.DrawRuntimeException;
import org.djutils.draw.bounds.Bounds2d;
import org.djutils.draw.point.Point2d;
import org.junit.Test;
/**
* Segment2dTest.java.
*
* Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See DJUTILS License.
*
* @author Alexander Verbraeck
* @author Peter Knoppers
*/
public class LineSegment2dTest
{
/**
* Test the constructors.
*/
@Test
public void constructorTest()
{
verifySegment("Segment from four coordinates", new LineSegment2d(1, 2, 3, 4), 1, 2, 3, 4);
verifySegment("Segment from two coordinates and a Point2d", new LineSegment2d(1, 2, new Point2d(3, 4)), 1, 2, 3, 4);
verifySegment("Segment from a Point2d and two coordinates", new LineSegment2d(new Point2d(1, 2), 3, 4), 1, 2, 3, 4);
verifySegment("Segment from two Point2d objects", new LineSegment2d(new Point2d(1, 2), new Point2d(3, 4)), 1, 2, 3, 4);
try
{
new LineSegment2d(1, 2, 1, 2);
fail("idential start and end should have thrown a DrawRuntimeException");
}
catch (DrawRuntimeException dre)
{
// Ignore expected exception
}
new LineSegment2d(1, 2, 1, 3);
new LineSegment2d(1, 2, 0, 2);
}
/**
* Check that a segment has all the right values.
* @param description String; description of the test
* @param segment Segment2d; the segment
* @param expectedStartX double; the expected x value for the start of the segment
* @param expectedStartY double; the expected y value for the start of the segment
* @param expectedEndX double; the expected x value for the end of the segment
* @param expectedEndY double; the expected y value for the end of the segment
*/
public void verifySegment(final String description, final LineSegment2d segment, final double expectedStartX,
final double expectedStartY, final double expectedEndX, final double expectedEndY)
{
assertEquals(description + " startX", expectedStartX, segment.startX, 0.0001);
assertEquals(description + " startY", expectedStartY, segment.startY, 0.0001);
assertEquals(description + " endX", expectedEndX, segment.endX, 0.0001);
assertEquals(description + " endY", expectedEndY, segment.endY, 0.0001);
assertEquals(description + " getStartPoint x", expectedStartX, segment.getStartPoint().x, 0.0001);
assertEquals(description + " getStartPoint y", expectedStartY, segment.getStartPoint().y, 0.0001);
assertEquals(description + " getEndPoint x", expectedEndX, segment.getEndPoint().x, 0.0001);
assertEquals(description + " getEndPoint y", expectedEndY, segment.getEndPoint().y, 0.0001);
assertEquals(description + " length", Math.hypot(expectedEndX - expectedStartX, expectedEndY - expectedStartY),
segment.getLength(), 0.0001);
assertEquals(description + " size is 2", 2, segment.size());
Iterator extends Point2d> iterator = segment.getPoints();
assertTrue(description + " iterator has data", iterator.hasNext());
Point2d point = iterator.next();
assertEquals(description + " iterator first point x", expectedStartX, point.x, 0.0001);
assertEquals(description + " iterator first point y", expectedStartY, point.y, 0.0001);
assertTrue(description + " iterator has more data", iterator.hasNext());
point = iterator.next();
assertEquals(description + " iterator second point x", expectedEndX, point.x, 0.0001);
assertEquals(description + " iterator second point y", expectedEndY, point.y, 0.0001);
assertFalse(description + " iterator has no more data", iterator.hasNext());
Bounds2d bounds = segment.getBounds();
assertEquals(description + " bounds minX", Math.min(expectedStartX, expectedEndX), bounds.getMinX(), 0.0001);
assertEquals(description + " bounds maxX", Math.max(expectedStartX, expectedEndX), bounds.getMaxX(), 0.0001);
assertEquals(description + " bounds minY", Math.min(expectedStartY, expectedEndY), bounds.getMinY(), 0.0001);
assertEquals(description + " bounds maxY", Math.max(expectedStartY, expectedEndY), bounds.getMaxY(), 0.0001);
assertTrue(description + " toString returns something descriptive", segment.toString().startsWith("LineSegment2d "));
assertTrue(description + " toString can suppress the class name",
segment.toString().indexOf(segment.toString(true)) > 0);
}
/**
* Test the getLocation methods.
*/
@Test
public void locationTest()
{
Point2d startPoint = new Point2d(3, 4);
Point2d endPoint = new Point2d(9, 20);
LineSegment2d segment = new LineSegment2d(startPoint, endPoint);
try
{
segment.getLocation(Double.NaN);
fail("NaN position should have thrown a DrawRuntimeException");
}
catch (DrawRuntimeException dre)
{
// Ignore expected exception
}
try
{
segment.getLocationExtended(Double.POSITIVE_INFINITY);
fail("Infinity position should have thrown a DrawRuntimeException");
}
catch (DrawRuntimeException dre)
{
// Ignore expected exception
}
try
{
segment.getLocationExtended(Double.NEGATIVE_INFINITY);
fail("Infinity position should have thrown a DrawRuntimeException");
}
catch (DrawRuntimeException dre)
{
// Ignore expected exception
}
for (double position : new double[] { -3, -0.5, 0, 1, 10, 100 })
{
if (position < 0 || position > segment.getLength())
{
try
{
segment.getLocation(position);
fail("position out of bounds should have thrown a DrawRuntimeException");
}
catch (DrawRuntimeException dre)
{
// Ignore expected exception
}
}
else
{
Ray2d ray = segment.getLocation(position);
assertEquals("distance from start point", position, ray.distance(startPoint), 0.0001);
assertEquals("distance from end point", segment.getLength() - position, ray.distance(endPoint), 0.0001);
assertEquals("direction of ray", startPoint.directionTo(endPoint), ray.phi, 0.0001);
}
Ray2d ray = segment.getLocationExtended(position);
assertEquals("distance from start point", Math.abs(position), ray.distance(startPoint), 0.0001);
assertEquals("distance from end point", Math.abs(segment.getLength() - position), ray.distance(endPoint), 0.0001);
assertEquals("direction of ray", startPoint.directionTo(endPoint), ray.phi, 0.0001);
}
}
/**
* Test the closestPointOnSegment method.
*/
@Test
public void closestPointOnSegmentTest()
{
LineSegment2d segment = new LineSegment2d(1, 2, 20, 10);
try
{
segment.closestPointOnSegment(null);
fail("Null for point should have thrown a NullPointerException");
}
catch (NullPointerException npe)
{
// Ignore expected exception
}
Point2d result = segment.closestPointOnSegment(new Point2d(1, 0));
assertEquals("result is start point", segment.startX, result.x, 0);
assertEquals("result is start point", segment.startY, result.y, 0);
result = segment.closestPointOnSegment(new Point2d(0, 2));
assertEquals("result is start point", segment.startX, result.x, 0);
assertEquals("result is start point", segment.startY, result.y, 0);
result = segment.closestPointOnSegment(new Point2d(1, 2));
assertEquals("result is start point", segment.startX, result.x, 0);
assertEquals("result is start point", segment.startY, result.y, 0);
Point2d projectingPoint = new Point2d(10, 10);
result = segment.closestPointOnSegment(projectingPoint); // Projects at a point along the segment
double distanceFromStart = result.distance(segment.getStartPoint());
assertTrue("distance from start is > 0", distanceFromStart > 0);
double distanceToEnd = result.distance(segment.getEndPoint());
assertTrue("distance to end point is > 0", distanceToEnd > 0);
assertEquals("sum of distances is length of segment", segment.getLength(), distanceFromStart + distanceToEnd, 0.0001);
// Angle startPoint-result-test-projectingPoint should be 90 degrees
double angle = segment.getStartPoint().directionTo(segment.getEndPoint()) - result.directionTo(projectingPoint);
assertEquals("angle should be about 90 degrees", Math.PI / 2, Math.abs(AngleUtil.normalizeAroundZero(angle)), 0.0001);
result = segment.closestPointOnSegment(new Point2d(21, 10));
assertEquals("result is end point", segment.endX, result.x, 0);
assertEquals("result is end point", segment.endY, result.y, 0);
result = segment.closestPointOnSegment(new Point2d(20, 11));
assertEquals("result is end point", segment.endX, result.x, 0);
assertEquals("result is end point", segment.endY, result.y, 0);
result = segment.closestPointOnSegment(new Point2d(20, 10));
assertEquals("result is end point", segment.endX, result.x, 0);
assertEquals("result is end point", segment.endY, result.y, 0);
}
/**
* Test the project methods.
*/
@Test
public void testProject()
{
LineSegment2d segment = new LineSegment2d(1, 2, 20, 10);
assertTrue("projects outside", Double.isNaN(segment.projectOrthogonalFractional(new Point2d(1, 1))));
assertTrue("projects before start", segment.projectOrthogonalFractionalExtended(new Point2d(1, 1)) < 0);
assertEquals("projects at -2", -2,
segment.projectOrthogonalFractionalExtended(new Point2d(1 - 19 - 19 + 8, 2 - 8 - 8 - 19)), 0.0001);
assertEquals("point near half way (not on segment) project at about half way", 0.5,
segment.projectOrthogonalFractional(new Point2d(11, 1)), 0.1);
assertTrue("projects outside", Double.isNaN(segment.projectOrthogonalFractional(new Point2d(25, 15))));
assertTrue("projects after end", segment.projectOrthogonalFractionalExtended(new Point2d(25, 15)) > 1);
assertEquals("projects at 2", 2,
segment.projectOrthogonalFractionalExtended(new Point2d(1 + 19 + 19 - 8, 2 + 8 + 8 + 19)), 0.0001);
}
/**
* Test the toExcel method.
* @throws NumberFormatException if that happens, this test has failed
*/
@Test
public void testToExcel() throws NumberFormatException
{
LineSegment2d segment = new LineSegment2d(1, 2, 20, 10);
String result = segment.toExcel();
String[] lines = result.split("\n");
assertEquals("result is two lines", 2, lines.length);
for (int lineNo = 0; lineNo < lines.length; lineNo++)
{
String[] fields = lines[lineNo].trim().split("\t");
assertEquals("Line consists of two fields", 2, fields.length);
for (int fieldNo = 0; fieldNo < fields.length; fieldNo++)
{
double value = Double.parseDouble(fields[fieldNo]);
double expectedValue = lineNo == 0 ? (fieldNo == 0 ? segment.startX : segment.startY)
: (fieldNo == 0 ? segment.endX : segment.endY);
assertEquals("field contains the correct value", expectedValue, value, 0.0001);
}
}
}
/**
* Test the equals and hasCode methods.
*/
@Test
public void equalsAndHashCodeTest()
{
LineSegment2d segment = new LineSegment2d(1, 2, -3, -4);
assertEquals("equal to itself", segment, segment);
assertNotEquals("not equal to null", segment, null);
assertNotEquals("not equal to a totally different object", segment, "no way");
assertNotEquals("not equal to line segment with different start x", segment, new LineSegment2d(2, 2, -3, -4));
assertNotEquals("not equal to line segment with different start y", segment, new LineSegment2d(1, 3, -3, -4));
assertNotEquals("not equal to line segment with different end x", segment, new LineSegment2d(1, 2, -4, -4));
assertNotEquals("not equal to line segment with different end y", segment, new LineSegment2d(1, 2, -3, -5));
assertEquals("equal to another line segment with same start and end x, y", segment, new LineSegment2d(1, 2, -3, -4));
assertNotEquals("hashCode depends on start x", segment.hashCode(), new LineSegment2d(2, 2, -3, -4));
assertNotEquals("hashCode depends on start y", segment.hashCode(), new LineSegment2d(1, 3, -3, -4));
assertNotEquals("hashCode depends on end x", segment.hashCode(), new LineSegment2d(1, 2, -4, -4));
assertNotEquals("hashCode depends on end y", segment.hashCode(), new LineSegment2d(1, 2, -4, -5));
}
}