Question

I am trying to use the PathIterator to calculate the center of any Shape object, so that curved paths can be accounted for, but upon finding the center of a standard 1x1 rectangle, my getCenter() method returns the point:

Point2D.Double[0.3333333333333333, 0.3333333333333333]

My getCenter() method:

shape = new Rectangle2D.Double(0, 0, 1, 1);


public Point2D.Double getCenter()
        {
            ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
            double[] arr = new double[6];
            for(PathIterator pi = shape.getPathIterator(null); !pi.isDone(); pi.next())
            {
                pi.currentSegment(arr);
                points.add(new Point2D.Double(arr[0], arr[1]));
            }

            double cX = 0;
            double cY = 0;
            for(Point2D.Double p : points)
            {
                cX += p.x;
                cY += p.y;
            }
                    System.out.println(points.toString());
            return new Point2D.Double(cX / points.size(), cY / points.size());
        }

I have discovered that upon printing points.toString(), I get this in the Console:

[Point2D.Double[0.0, 0.0], Point2D.Double[1.0, 0.0], Point2D.Double[1.0, 1.0], Point2D.Double[0.0, 1.0], Point2D.Double[0.0, 0.0], Point2D.Double[0.0, 0.0]]

I noticed that there are six entries in the points array, as opposed to four which I was expecting, given that the input Shape object is Rectangle2D.Double(0, 0, 1, 1). Obviously it is accounting for the point (0, 0) two more times than I want it to, and I am confused as to why that is. Is it a result of the PathIterator.isDone() method? Am I using it incorrectly? What would solve my problem if PathIterator can't?

Was it helpful?

Solution

As it was already pointed out, the PathIterator returns different types of segments. When only considering the points that are involved in SEG_LINETO, you should already obtain satisfactory results. However, consider that there may also be SEG_QUADTO and SEG_CUBICTO in other shapes. These can easily be avoided by using a flattening PathIterator: When you create a PathIterator with

PathIterator pi = shape.getPathIterator(null, flatness);

with an appropriate flatness, then it will only contain straight line segments.

import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

public class ShapeCenter
{
    public static void main(String[] args)
    {
        System.out.println(computeCenter(new Ellipse2D.Double(-10,-10,20,20)));
        System.out.println(computeCenter(new Rectangle2D.Double(0,0,1,1)));
    }

    public static Point2D computeCenter(Shape shape)
    {
        final double flatness = 0.1;
        PathIterator pi = shape.getPathIterator(null, flatness);
        double coords[] = new double[6];
        double sumX = 0;
        double sumY = 0;
        int numPoints = 0;
        while (!pi.isDone())
        {
            int s = pi.currentSegment(coords);
            switch (s)
            {
                case PathIterator.SEG_MOVETO:
                    // Ignore
                    break;

                case PathIterator.SEG_LINETO:
                    sumX += coords[0]; 
                    sumY += coords[1]; 
                    numPoints++;
                    break;

                case PathIterator.SEG_CLOSE:
                    // Ignore
                    break;

                case PathIterator.SEG_QUADTO:
                    throw new AssertionError(
                        "SEG_QUADTO in flattening path iterator");
                case PathIterator.SEG_CUBICTO:
                    throw new AssertionError(
                        "SEG_CUBICTO in flattening path iterator");
            }
            pi.next();
        }
        double x = sumX / numPoints;
        double y = sumY / numPoints;
        return new Point2D.Double(x,y);
    }

}

OTHER TIPS

PathIterator defines different types of segments, you should pay attention to this fact. You get 6 segments in your example, because it returns additionally also the SEG_MOVETO segment, which defines the start of a subpath, and SEG_CLOSE at the end of the sub path. If you just want to get the endpoints of the lines of your shape, you should change your code like this:

    for(PathIterator pi = shape.getPathIterator(null); !pi.isDone(); pi.next())
    {
        if(pi.currentSegment(arr) == PathIterator.SEG_LINETO) {
            points.add(new Point2D.Double(arr[0], arr[1]));
        }
    }

I am not sure you're using it incorrectly but you aren't accounting for an aspect of PathIterator. PathIterator doesn't so much represent a geometric shape but rather a path that should be taken while drawing. So its points also represent the type of path the 'pen' should take. For example, for a Rectangle, the path makes the following segments:

  1. SEG_MOVETO
  2. SEG_LINETO
  3. SEG_LINETO
  4. SEG_LINETO
  5. SEG_LINETO
  6. SEG_CLOSE

Because obviously the path should:

  • Move, not draw, from wherever the pen was before.
  • Close off this path from whatever the pen draws next.

The type of segment is the return value of currentSegment. If you only want to capture points that are on the polygon you can check for the 'line to' segment:

if(pi.currentSegment(arr) == PathIterator.SEG_LINETO) {
    points.add(new Point2D.Double(arr[0], arr[1]));
}

That will work for simple polygons like Rectangle. For the given Rectangle, it will return [0.5, 0.5] which is I assume the result you're interested in.

On the other hand, there are Shapes that are not polygons so I'd be careful with this approach.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top