// 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); } }