/**
* 多边形,此多边形使用有序的节点{@link Node}表示 ,并使用等分点进行分割
*
* @author teacher.jia
*
*/
public class Polygon {
// 多边形节点
private final List nodes;
// 等分份数
private final int k;
// 周长
private double perimeter;
// 等分长度
private double avgDistance;
// 等分点列表,此列表可以作为组成新多边形的节点列表使用
private List equalNodes;
public Polygon(List nodes, int k) {
if (nodes.size() < 3) {
throw new IllegalArgumentException("the edges must greater than 2");
}
this.k = k;
this.nodes = initNodes(nodes);
initDatas();
equalNodes = CalculateUtil.equalNodes(this);
}
/*
* 对于节点列表序列赋予循环关系
*/
private void initOrderNodes(List nodes) {
for (int i = 0, len = nodes.size(); i < len; i++) {
if (i == len - 1) {
nodes.get(i).next = nodes.get(0);
} else {
nodes.get(i).next = nodes.get(i + 1);
}
}
}
/*
* 对输入的Node列表进行处理,对于参数nodes中处于两个节点线段间的节点应于剔除
* 此使用方法使用斜率计算来实现处于中间冗余节点的处理
*/
private List initNodes(List nodes) {
List temp = new ArrayList<>(nodes);
initOrderNodes(temp);
Set exclusiveNodes = new HashSet<>();
for (int i = 0; i < temp.size(); i++) {
Node node = temp.get(i);
double slope1 = CalculateUtil.slope(node, node.next);
double slope2 = CalculateUtil.slope(node, node.next.next);
if (slope1 == slope2)
exclusiveNodes.add(node.next);
}
if (exclusiveNodes.size() > 0) {
temp.removeAll(exclusiveNodes);
initOrderNodes(temp);
}
return temp;
}
/*
* 计算周长和等分长度
*/
private void initDatas() {
for (Node node : nodes) {
perimeter += node.distance();
}
avgDistance = perimeter / k;
}
/**
* 复制多边形的等分节点并返回
* @return 等分节点的副本
*/
public List getEqualNodes() {
return new ArrayList<>(equalNodes);
}
/*
* 辅助多边形的计算
*/
private static class CalculateUtil {
/*
* 由于使用Node计算,Node采用基本类型double 值进行计算,再做运算会有精度丢失,给与精度处理的误差作为指导
*/
private static final double precision = Math.pow(0.1, 3);
/*
* 计算两个节点的斜率
*/
static double slope(Node start, Node end) {
return start.y - end.y == 0 ? 0 : (start.x - end.x) / (start.y - end.y);
}
/*
* 计算两个节点之间的距离
*/
static double distance(Node node) {
return Math.sqrt(Math.pow((node.x - node.next.x), 2) + Math.pow((node.y - node.next.y), 2));
}
/*
* 计算等分节点,如果等到的等分节点与当前节点的下一个节点趋近范围在精度误差范围内,则使用下一个节点
*/
static Node fixedNode(Node node, double avgDistance) {
double rate = avgDistance / node.distance();
double x = node.x + rate * (node.next.x - node.x);
double y = node.y + rate * (node.next.y - node.y);
if (Math.abs(x - node.next.x) < precision && Math.abs(y - node.next.y) < precision) {
return new Node(node.next);
} else {
return new Node(x, y);
}
}
/*
* 计算等分节点的集合
*/
static List equalNodes(Polygon polygon) {
List cNodes = new ArrayList<>(polygon.nodes);
double cAvgDistance = polygon.avgDistance;
List equalNodes = new ArrayList<>(cNodes.size());
for (int i = 0; i < cNodes.size(); i++) {
Node node = cNodes.get(i);
if (cAvgDistance - node.distance() >= CalculateUtil.precision) {
cAvgDistance -= node.distance();
} else {
Node fiexedNode = fixedNode(node, cAvgDistance);
equalNodes.add(fiexedNode);
if (fiexedNode.x != node.next.x || fiexedNode.y != node.next.y) {
Node newNode = new Node(fiexedNode);
cNodes.set(i, newNode);
--i;
}
cAvgDistance = polygon.avgDistance;
}
}
return equalNodes;
}
}
/**
* 多边形的节点,坐标为基本类型double,如果需要精确计算则使用BigDecimal
* 该类不提供equals方法,如果需要比较节点则使用外部工具,这样可以屏蔽bean对精度的关注
* @author teacher.jia
*
*/
public static class Node {
final double x;
final double y;
Node next;
Node(double x, double y) {
this.x = x;
this.y = y;
}
Node(Node node) {
this(node.x, node.y);
}
public void next(Node next) {
}
double distance() {
return CalculateUtil.distance(this);
}
@Override
public String toString() {
return "Node [x=" + x + ", y=" + y + "]";
}
}
}