/*
* Copyright (c) 2017 wetransform GmbH
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* wetransform GmbH <http://www.wetransform.to>
*/
package eu.esdihumboldt.util.geometry.interpolation.model.impl;
import static eu.esdihumboldt.util.geometry.interpolation.InterpolationUtil.round;
import java.text.MessageFormat;
import com.vividsolutions.jts.geom.Coordinate;
import eu.esdihumboldt.util.geometry.interpolation.model.Angle;
import eu.esdihumboldt.util.geometry.interpolation.model.ArcByCenterPoint;
import eu.esdihumboldt.util.geometry.interpolation.model.ArcByPoints;
/**
* An arc represented by three points.
*
* @author Simon Templer
*/
public class ArcByPointsImpl implements ArcByPoints {
private final Coordinate startPoint;
private final Coordinate middlePoint;
private final Coordinate endPoint;
private ArcByCenterPoint centerRepresentation;
/**
* Create an Arc represented by three points.
*
* @param startPoint the arc start point
* @param middlePoint the arc middle point (on the arc)
* @param endPoint the arc end point
*/
public ArcByPointsImpl(Coordinate startPoint, Coordinate middlePoint, Coordinate endPoint) {
super();
this.startPoint = startPoint;
this.middlePoint = middlePoint;
this.endPoint = endPoint;
}
@Override
public boolean isCircle() {
return startPoint.equals(endPoint);
}
@Override
public ArcByCenterPoint toArcByCenterPoint() {
if (centerRepresentation == null) {
Coordinate centerPoint = calculateCenterPoint();
double radius = Math.sqrt(Math.pow((startPoint.x - centerPoint.x), 2)
+ Math.pow((startPoint.y - centerPoint.y), 2));
Angle startAngle = Angle.angle(centerPoint, startPoint);
Angle endAngle = Angle.angle(centerPoint, endPoint);
boolean clockwise = isClockwise(centerPoint);
centerRepresentation = new ArcByCenterPointImpl(centerPoint, radius, startAngle,
endAngle, clockwise);
}
return centerRepresentation;
}
/**
* Determine if the arc is clockwise.
*
* @author Arun Verma
* @param centerPoint the arc center point
* @return <code>true</code> if the arc is clockwise, false otherwise
*/
private boolean isClockwise(Coordinate centerPoint) {
boolean cw = true;
double c1Angle = Math.atan2(startPoint.y - centerPoint.y, startPoint.x - centerPoint.x);
c1Angle = c1Angle * 360 / (2 * Math.PI);
double c2Angle = Math.atan2(middlePoint.y - centerPoint.y, middlePoint.x - centerPoint.x);
c2Angle = c2Angle * 360 / (2 * Math.PI);
double c3Angle = Math.atan2(endPoint.y - centerPoint.y, endPoint.x - centerPoint.x);
c3Angle = c3Angle * 360 / (2 * Math.PI);
if (c1Angle > 0) {
if (c2Angle > 0) {
if (c3Angle > 0) {
if (c3Angle > c2Angle && c2Angle > c1Angle) {
cw = false;
}
}
else { // c3Angle <0
if (c2Angle > c1Angle) {
cw = false;
}
}
}
else { // c2Angle<0
if (c3Angle > 0) {
if (c3Angle < c1Angle) {
cw = false;
}
}
else { // c3Angle <0
if (c2Angle < c3Angle) {
cw = false;
}
}
}
}
else { // c1Angle <0
if (c2Angle > 0) {
if (c3Angle > 0) {
if (c3Angle > c2Angle) {
cw = false;
}
}
else { // c3Angle <0
if (c3Angle < c1Angle) {
cw = false;
}
}
}
else { // c2Angle<0
if (c3Angle > 0) {
if (c2Angle > c1Angle) {
cw = false;
}
}
else { // c3Angle <0
if (c2Angle > c1Angle) {
if (c3Angle > c2Angle) {
cw = false;
}
else if (c3Angle < c1Angle) {
cw = false;
}
}
else { // c2Angle < C1Angle
if (c3Angle > c2Angle) {
cw = false;
}
}
}
}
}
return cw;
}
@Override
public Coordinate getStartPoint() {
return startPoint;
}
@Override
public Coordinate getEndPoint() {
return endPoint;
}
@Override
public Coordinate getMiddlePoint() {
return middlePoint;
}
/**
* Calculate the center point.
*
* @author Arun Verma
* @return the center point
*/
protected Coordinate calculateCenterPoint() {
// we can get two lines using above slope let's say p1p2: (ya =
// m_a(x1-x0) + y0) and p2p3: (yb = m_a(x2-x1) + y1)
Coordinate AB_Mid = new Coordinate((startPoint.x + middlePoint.x) / 2,
(startPoint.y + middlePoint.y) / 2);
if (startPoint.equals(endPoint)) {
// start is end point -> circle
return AB_Mid;
}
Coordinate BC_Mid = new Coordinate((middlePoint.x + endPoint.x) / 2,
(middlePoint.y + endPoint.y) / 2);
// The center of the circle is the intersection of the two lines
// perpendicular to and passing through the midpoints of the lines p1p2
// and p2p3.
double yDelta_a = middlePoint.y - startPoint.y;
double xDelta_a = middlePoint.x - startPoint.x;
double yDelta_b = endPoint.y - middlePoint.y;
double xDelta_b = endPoint.x - middlePoint.x;
double aSlope = yDelta_a / xDelta_a;
double bSlope = yDelta_b / xDelta_b;
double centerX = 0;
double centerY = 0;
if (yDelta_a == 0) // aSlope == 0
{
centerX = AB_Mid.x;
if (xDelta_b == 0) // bSlope == INFINITY
{
centerY = BC_Mid.y;
}
else {
centerY = BC_Mid.y + (BC_Mid.x - centerX) / bSlope;
}
}
else if (yDelta_b == 0) // bSlope == 0
{
centerX = BC_Mid.x;
if (xDelta_a == 0) // aSlope == INFINITY
{
centerY = AB_Mid.y;
}
else {
centerY = AB_Mid.y + (AB_Mid.x - centerX) / aSlope;
}
}
else if (xDelta_a == 0) // aSlope == INFINITY
{
centerY = AB_Mid.y;
centerX = bSlope * (BC_Mid.y - centerY) + BC_Mid.x;
}
else if (xDelta_b == 0) // bSlope == INFINITY
{
centerY = BC_Mid.y;
centerX = aSlope * (AB_Mid.y - centerY) + AB_Mid.x;
}
else //
{
if (round(aSlope, 4) != round(bSlope, 4)) {
centerX = ((aSlope * bSlope * (BC_Mid.y - AB_Mid.y)) + (aSlope * BC_Mid.x)
- (bSlope * AB_Mid.x)) / (aSlope - bSlope);
centerY = AB_Mid.y - ((centerX - AB_Mid.x) / aSlope);
}
else {
centerX = Double.POSITIVE_INFINITY;
centerY = Double.POSITIVE_INFINITY;
}
}
return new Coordinate(centerX, centerY);
}
@Override
public String toString() {
return MessageFormat.format("Arc({0}, {1}, {2})", startPoint, middlePoint, endPoint);
}
}