6

I am struggling with this part of my task at work. I've deliberately not detailed the context of the work task to try and keep the focus on the problem. I have to merge rectangles into a single polygon as shown in the attached image, but I need the list of points so that I can write these out into Polygon shape (DOM object) for a Swing canvas and then SVG export.

enter image description here

I know the origin of each rectangle, i.e. the upper left x and y coordinates (float x, float y) and also the width (float) and height (float) of each rectangle, so from this I can calculate the coordinates of all four corners of each rectangle, i.e. top, right, bottom, left, i.e. top = origin = x, y, right = x + width, bottom = x + width, y + height and left = x, y + height.

I have a List<Rectangle> rectangles and would like an algorithm which will translate this list into a single polygon (List<Points> where a point represents the coordinates (x, y) of each point as shown in the diagram marked red "x"s.

I will then use this list of points to write out an element in the DOM for printing a web page eventually in SVG. So, my end result has to be a list of points (i.e. x,y coordinates for constructing a polygon shape in SVG).

I did see this answer which does something similar, but I'm not sure if I can apply this to my case - also it's written in Python and not Java: Merging multiple adjacent rectangles into one polygon

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
robbie70
  • 1,515
  • 4
  • 21
  • 30
  • 1
    Are you allowed to use the inbuilt APIs or this some kind school assignment? – MadProgrammer Mar 03 '17 at 00:41
  • which in-built APIs ? no this not a school assignment - this is part of a task I have to do for work. Why has the question been down voted already ?! – robbie70 Mar 03 '17 at 00:48
  • Because you are asking people to give you the answer instead of trying to code it yourself. – Lavaman65 Mar 03 '17 at 00:51
  • You can use `Area` to `add` shapes together. Assuming you're using something like `java.awt.Rectangle`, then you can wrap it in a `Area` and `add` it to another `Area` which represents the final state. You might also have a look `java.awt.Rectangle`, as it has a number of `add` methods as well – MadProgrammer Mar 03 '17 at 00:52
  • I have made several attempts at coding it myself and I was not happy with my solution - it didnt work correctly and I dont think it would form the basis of a solution so I decided to bin it and start again. I have read up an algorithm by Sedgewick but this seems overly complicated for my task. I was hoping to get some pointers into the right direction at least. – robbie70 Mar 03 '17 at 00:54
  • Thanks MadProgrammer. I did have a look at Rectangle Awt especially the Intersection method but this is just a boolean and I didnt really think it could help me. I will check-out the Area class to see how it works. I am not really using AWT - the Rectangle class I mentioned in my question above is my own defined class - as is the Point class mentioned and the Polygon. – robbie70 Mar 03 '17 at 00:56
  • I worked on a simpler problem to find the intersection of two rectangles, and I found it simplified things if I split it into multiple cases, considering what happened when one rectangle was completely to the left, right, top, bottom, containing lengthwise, containing height wise (and perhaps a few other cases) of the other. – David Choweller Mar 03 '17 at 01:00
  • Will the Area method give me the points though ? My end goal is to produce a Polygon and print this in SVG file - so I need the list of Points in order to render this in SVG. – robbie70 Mar 03 '17 at 01:02
  • @David Choweller thanks. My first attempt was along these lines - but it produced some horrible code and didnt cover all my cases so I would like to look for a more robust solution. – robbie70 Mar 03 '17 at 01:08
  • I found that it helped my thinking process if I split the cases into multiple methods and named them in very intuitive ways, instead of putting all the messy calculations in one big method. – David Choweller Mar 03 '17 at 01:09
  • It seems like the rectangles in the diagram are missing a case that could complicate things: when you have 3 rectangles that all intersect each other. Maybe this is an indication that you can handle the cases two rectangles at a time. – David Choweller Mar 03 '17 at 01:21

2 Answers2

6

Here is a solution my colleague and I came up with. Hope it can help someone else.

public class PolygonHelper {

    public Polygon makePolygon(List<Rectangle> rectangles){
        List<Point> points = calcPoints(rectangles);
        return new Polygon(points);
    }

    private List<Point> calcPoints(List<Rectangle> rectangles) {
        List<Point> ret = new ArrayList<>();

        List<Float> yCoords = new ArrayList<>(getAllYCoords(rectangles));
        yCoords.sort(Comparator.naturalOrder());

        float previousLeftCoord = 0;
        float previousRightCoord = 0;

        for(float yCoord : yCoords) {
            System.out.println("Considering yCoords "+ yCoord);
            float minimumXLeftCoord = minXLeftCoord(yCoord, rectangles);
            float maximumXRightCoord = maxXRightCoord(yCoord, rectangles);
            System.out.println("min X: "+minimumXLeftCoord);
            System.out.println("max X: "+maximumXRightCoord);

            if(yCoord == yCoords.get(0)) {
                ret.add(new Point(minimumXLeftCoord, yCoord));
                ret.add(new Point(maximumXRightCoord, yCoord));

            } else {

                if(minimumXLeftCoord!=previousLeftCoord) {
                    ret.add(0, new Point(previousLeftCoord, yCoord));
                    ret.add(0, new Point(minimumXLeftCoord, yCoord));
                } else {
                    ret.add(0, new Point(minimumXLeftCoord, yCoord));
                }

                if(maximumXRightCoord!=previousRightCoord) {
                    ret.add(new Point(previousRightCoord, yCoord));
                    ret.add(new Point(maximumXRightCoord, yCoord));
                } else {
                    ret.add(new Point(maximumXRightCoord, yCoord));
                }

            }

            previousLeftCoord = minimumXLeftCoord;
            previousRightCoord = maximumXRightCoord;
            System.out.println(ret);
        }

        return ret;

    }

    private Set<Float> getAllYCoords(List<Rectangle> rectangles) {
        List<Float> allBottomYCoords = rectangles.stream().map(rectangle -> rectangle.getBottom().getY()).collect(Collectors.toList());
        List<Float> allTopYCoords = rectangles.stream().map(rectangle -> rectangle.getTop().getY()).collect(Collectors.toList());

        Set<Float> allCoords = new HashSet<>();
        allCoords.addAll(allTopYCoords);
        allCoords.addAll(allBottomYCoords);
        return allCoords;
    }

    private float minXLeftCoord(Float y, List<Rectangle> rectangles) {
        return rectanglesAtY(y, rectangles).stream().map(rect -> rect.getLeft().getX()).min(Comparator.naturalOrder()).get();
    }

    private float maxXRightCoord(Float y, List<Rectangle> rectangles) {
        return rectanglesAtY(y, rectangles).stream().map(rect -> rect.getRight().getX()).max(Comparator.naturalOrder()).get();
    }

    private List<Rectangle> rectanglesAtY(Float y, List<Rectangle> rectangles) {
        List<Rectangle> rectsAtYExcBottomLines = rectsAtYExcBottomLines(y, rectangles);

        if(rectsAtYExcBottomLines.size()>0) {
            // there are rectangles that are not closing here, so ignore those that are closing.
            return rectsAtYExcBottomLines;
        } else {
            // there are only rectangle bottom lines so we need to consider them.
            return rectsAtYIncBottomLines(y, rectangles);
        }
    }

    private List<Rectangle> rectsAtYExcBottomLines(Float y, List<Rectangle> rectangles) {
        return rectangles.stream()
                .filter(rect -> rect.getTop().getY()<=y && rect.getBottom().getY()>y).collect(Collectors.toList());
    }

    private List<Rectangle> rectsAtYIncBottomLines(Float y, List<Rectangle> rectangles) {
        return rectangles.stream()
                .filter(rect -> rect.getTop().getY()<=y && rect.getBottom().getY()==y).collect(Collectors.toList());
    }

}
robbie70
  • 1,515
  • 4
  • 21
  • 30
1

You can do this much more simply by using Java's Area class

    Area area = new Area();
    area.add(new Area(rect1));
    area.add(new Area(rect2));
    area.add(new Area(rect3));

From there you can test for intersectons and area.contains as needed. If you want to convert it into a polygon or draw the result, grab the area.getPathIterator(null); and iterate over the vertexes.

Kyle Berezin
  • 597
  • 4
  • 20