// License: GPL. For details, see LICENSE file.
package pdfimport;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ParallelSegmentsFinder {
public double angle;
public double angleSum;
public int refCount;
public List<PdfPath> paths = new ArrayList<>();
public void addPath(PdfPath path, double angle2) {
angleSum += angle2;
paths.add(path);
angle = angleSum /paths.size();
}
public List<ParallelSegmentsFinder> splitByDistance(double maxDistance) {
//sort perpendicular to angle
AffineTransform tr = new AffineTransform();
tr.rotate(-angle);
final Map<PdfPath, Point2D> positions = new HashMap<>();
Point2D src = new Point2D.Double();
for (PdfPath path: paths) {
src.setLocation((path.firstPoint().getX() + path.lastPoint().getX()) / 2, (path.firstPoint().getY() + path.lastPoint().getY()) / 2);
Point2D dest = new Point2D.Double();
Point2D destA = new Point2D.Double();
Point2D destB = new Point2D.Double();
tr.transform(src, dest);
tr.transform(path.firstPoint(), destA);
tr.transform(path.lastPoint(), destB);
positions.put(path, dest);
}
//point.y = Perpendicular lines, point.x = parallel lines
Collections.sort(paths, new Comparator<PdfPath>() {
@Override
public int compare(PdfPath o1, PdfPath o2) {
double y1 = positions.get(o1).getY();
double y2 = positions.get(o2).getY();
return y1 > y2 ? 1 : y1 < y2 ? -1 : 0;
}
});
//process sweep
List<ParallelSegmentsFinder> result = new ArrayList<>();
Map<PdfPath, ParallelSegmentsFinder> sweepLine = new HashMap<>();
Set<ParallelSegmentsFinder> adjacentClustersSet = new HashSet<>();
List<ParallelSegmentsFinder> adjacentClusters = new ArrayList<>();
List<PdfPath> pathsToRemove = new ArrayList<>();
for (PdfPath path: paths) {
adjacentClusters.clear();
adjacentClustersSet.clear();
pathsToRemove.clear();
for (PdfPath p: sweepLine.keySet()) {
Point2D pathPos = positions.get(path);
Point2D pPos = positions.get(p);
if (pathPos.getY() - pPos.getY() > maxDistance) {
//path too far from sweep line
pathsToRemove.add(p);
} else {
double distance = distanceLineLine(path, p);
if (distance <= maxDistance) {
if (adjacentClustersSet.add(sweepLine.get(p))) {
adjacentClusters.add(sweepLine.get(p));
}
}
}
}
//remove segments too far apart
for (PdfPath p: pathsToRemove) {
ParallelSegmentsFinder finder = sweepLine.remove(p);
finder.refCount--;
if (finder.refCount == 0) {
result.add(finder);
}
}
//join together joinable parts
if (adjacentClusters.size() > 0) {
ParallelSegmentsFinder finder = adjacentClusters.remove(0);
finder.paths.add(path);
sweepLine.put(path, finder);
finder.refCount++;
for (ParallelSegmentsFinder finder1: adjacentClusters) {
for (PdfPath path1: finder1.paths) {
finder.paths.add(path1);
sweepLine.put(path1, finder);
finder.refCount++;
}
}
} else {
ParallelSegmentsFinder finder = new ParallelSegmentsFinder();
finder.addPath(path, angle);
sweepLine.put(path, finder);
finder.refCount = 1;
}
}
//process remaining paths in sweep line
for (PdfPath p: sweepLine.keySet()) {
ParallelSegmentsFinder finder = sweepLine.get(p);
finder.refCount--;
if (finder.refCount == 0) {
result.add(finder);
}
}
return result;
}
private double distanceLineLine(PdfPath p1, PdfPath p2) {
return distanceLineLine(p1.firstPoint(), p1.lastPoint(), p2.firstPoint(), p2.lastPoint());
}
private double distanceLineLine(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {
double dist1 = closestPointToSegment(p1, p2, p3).distance(p3);
double dist2 = closestPointToSegment(p1, p2, p4).distance(p4);
double dist3 = closestPointToSegment(p3, p4, p1).distance(p1);
double dist4 = closestPointToSegment(p3, p4, p2).distance(p2);
return Math.min(Math.min(dist1, dist2), Math.min(dist3, dist4));
}
/**
* Calculates closest point to a line segment.
* @return segmentP1 if it is the closest point, segmentP2 if it is the closest point,
* a new point if closest point is between segmentP1 and segmentP2.
*/
public static Point2D closestPointToSegment(Point2D segmentP1, Point2D segmentP2, Point2D point) {
double ldx = segmentP2.getX() - segmentP1.getX();
double ldy = segmentP2.getY() - segmentP1.getY();
if (ldx == 0 && ldy == 0) //segment zero length
return segmentP1;
double pdx = point.getX() - segmentP1.getX();
double pdy = point.getY() - segmentP1.getY();
double offset = (pdx * ldx + pdy * ldy) / (ldx * ldx + ldy * ldy);
if (offset <= 0)
return segmentP1;
else if (offset >= 1)
return segmentP2;
else
return new Point2D.Double(segmentP1.getX() + ldx * offset, segmentP1.getY() + ldy * offset);
}
}