// License: GPL. For details, see LICENSE file. package pdfimport; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; public final class DuplicateNodesFinder { private DuplicateNodesFinder() { // Hide default constructor for utilities classes } /*** * This method finds very close nodes and constructs a mapping from node to suggested representative node. * Works by performing a sweep and noting down similar nodes. * @param nodes the nodes to process * @return map from nodes that need replacement to a representative node. */ public static Map<Point2D, Point2D> findDuplicateNodes(Collection<Point2D> nodes, final double tolerance) { List<Point2D> points = new ArrayList<>(nodes); Collections.sort(points, new Comparator<Point2D>() { @Override public int compare(Point2D o1, Point2D o2) { double diff = o1.getY() - o2.getY(); return diff > 0 ? 1 : (diff < 0 ? -1 : 0); } }); Map<Point2D, Point2D> result = new HashMap<>(); TreeMap<Point2D, Point2D> sweepLine = new TreeMap<>(new Comparator<Point2D>() { @Override public int compare(Point2D o1, Point2D o2) { double diff = o1.getX() - o2.getX(); if (Math.abs(diff) <= tolerance) { return 0; } return diff > 0 ? 1 : (diff < 0 ? -1 : 0); } }); //sweep from top to bottom. double prevY = Double.NEGATIVE_INFINITY; for (Point2D point: points) { boolean mappedToOtherPoint = false; if (point.getY() - prevY > tolerance) { sweepLine.clear(); //big offset, clear old points } else { //small offset, test against existing points (there may be more than one) while (!mappedToOtherPoint && sweepLine.containsKey(point)) { //a close point found Point2D closePoint = sweepLine.get(point); double dy = point.getY() - closePoint.getY(); if (dy <= tolerance) { //mark them as close result.put(point, closePoint); mappedToOtherPoint = true; } else { sweepLine.remove(point); } } } if (!mappedToOtherPoint) { sweepLine.put(point, point); } prevY = point.getY(); } return result; } }