0

For polygon, the points are given in counter clock wise manner. For holes, the points are given in clock wise manner. So given a list of polygons, how to construct all the polygon with holes?

Here are a few precondition of the polygon list:

  1. One polygon can contain multiple holes, but a hole must be contained inside a polygon.
  2. A hole cannot contain one or more polygon.
  3. All the holes must be located inside polygon, and not outside of it
  4. Hole's edge can touch on polygon's.
  5. And all the polygons and holes must not intersect with other polygons/holes, although they can touch one another.
  6. The polygon/ hole can be either convex or concave.

How to devise an algorithm that construct polygons with holes?

I am looking for a general algorithm, so any code in the form of C#, C++ and matlab are welcomed.

Edit: This is my input ( in C#)

public class PolygonWithHoles
{
   public List<Point> OuterBoundary;
   public List<List<Point>> Holes;
}


//input: a list of point list. If the point list constructs a polygon in Counter clock wise, then it is a general polygon, else it is a holes
public List<PolygonWithHoles> ConstuctPolyHoles(List<List<Point>> Polys)
{
}
Graviton
  • 81,782
  • 146
  • 424
  • 602
  • 1
    It is not clear to me what output you want. Could you give an example? – Emond Jan 17 '11 at 07:22
  • @Erno, sample input/output added – Graviton Jan 17 '11 at 07:41
  • Are the polygons and/or holes convex? – Emond Jan 17 '11 at 07:45
  • @Erno, it can be convex *or* concave, does it matter? – Graviton Jan 17 '11 at 07:45
  • Yes, I think it does. Because when they can be concave it is hard to findout if a list of points is a hole or a polygon. – Emond Jan 17 '11 at 07:48
  • I still don't understand what kind of output you are expecting. Should each "PolygonWithHoles" describe a single separate polygon, with "its" holes being connected through "cut-lines" (that is, each hole is connected to the outer contour using two overlapping straight lines)? I've studied and implemented several polygon algorithms, so if you are a bit more specific, I may be able to give you some advice. – Daniel Gehriger Jan 17 '11 at 22:04
  • @Daniel, yes, each `PolygonWithHoles` describes a single separate polygons, with its holes being the holes ( point list that are of clock wise direction) that are located *inside* it. – Graviton Jan 18 '11 at 01:08
  • @Erno, it doesn't matter actually, just take a line from the query point to a given point outside the polygon, e.g. inf,inf, and count the number of intersecting lines of your polygon, if it's odd it's inside, if it's even it's outside. There are however two gotchas here, the one is polygon vertices falling onto your line, the second is floating point rounding errors as Daniel already mentioned. But convex or concave is not going to change anything about those gotchas. – wich Jan 19 '11 at 15:58
  • @wich, you are right. I forgot about the intersection trick. I was thinking about calculating the angles of two consecutive segments. Thanks. – Emond Jan 19 '11 at 17:57

2 Answers2

2

Point in polygon tests are vulnerable due to floating point rounding errors. They usually only work for non-trivial polygons.

A more robust approach should be based on a scan-line algorithm, where vertices are first sorted according to their x and y values. A scan (or sweep) line then moves eg. from left to right. The algorithm then typically maintains a list of lines intersecting the scan line by adding a line when its left vertex "hits" the scan line, and removing it when its right vertex hits the line.

After each move of the scan line, the intersections of the current lines with the scan line is updated, and the lines re-ordered according to the y value of the intersection. Whenever two lines need to be re-ordered during the sorting operation, then this means that they have an intersection, which can then be recorded.

After finding all the intersections, contours and holes can be identified reliably.

The following projects use this approach:

There are others, and the following site (which promotes the PolyBoolean library) compares the most important ones: http://www.complex-a5.ru/polyboolean/comp.html.

Just as a warning: I myself have implemented a polygon library supporting Boolean operations as well as hole detection. This library is being used in a commercial product, and I spent several years (!) to fine-tune the algorithm to ensure a correct result for any given input polygon in a little time as possible (other libraries may be a few % faster, but fail with some input data). In fact, a single approach algorithm may not be capable of solving all possible problems, so I had to implement several ones.

Good luck!

Daniel Gehriger
  • 7,339
  • 2
  • 34
  • 55
  • after giving you upvote I realize that you are just showing polygong clipping library-- it's not hole detection and it's not answering my question. – Graviton Jan 27 '11 at 06:43
  • @Graviton, the clipping libraries detect holes as part of their algorithm. For instance, Klaas Holwerda's *Boolean* library has a function `detectHoles()` (or similiar), which is called during the clipping process, and which categorizes polygons into holes and outer contours. In general, you also need the clipping part, because if polygons are self-intersecting, new vertices have to be added before detecting holes. If you don't need that, you can remove the corresponding stages in the algorithms. – Daniel Gehriger Jan 27 '11 at 07:00
  • not too sure about that. In my input, we already know that outer boundary is Counter Clock Wise and holes are clock wise. Does the clipping libraries take this into account? I don't think so. – Graviton Jan 27 '11 at 07:12
  • @Graviton. They do. That's what is called the *fill mode*. You would need to use the "non-zero winding rule" as the fill mode. But reading through your list of pre-conditions, I realize that the algorithms I suggested are overkill. I'll update my answer. – Daniel Gehriger Jan 27 '11 at 09:01
  • @Graviton. Actually, I once again thought about it, and I stay by my answer that you need a sweep line based algorithm. While it is simple to determine if vertices are clockwise or CCW, testing if a hole is in a given polygon is non-trivial, unless you are willing to settle for O(n^2) complexity. While none of the libraries I suggested will do exactly what you want out of the box, they can be modified to do so. My own library (C++ and .NET) does it out of the box, but unfortunately, it's not open source (I commercialize it). – Daniel Gehriger Jan 27 '11 at 10:12
0

Maybe you are looking for a simple solution similar to:

public sealed class PolygonWithHoles : Shape
{

    #region Constructors
    
    /// <summary>
    /// Instantiates a new instance of a polygon.
    /// </summary>
    public PolygonWithHoles()
    {
    }

    #endregion Constructors

    #region Dynamic Properties
    
    /// <summary>
    /// Holes property
    /// </summary>
    public static readonly DependencyProperty HolesProperty = DependencyProperty.Register(
            "Holes", typeof(List<PointCollection>), typeof(PolygonWithHoles),
            new FrameworkPropertyMetadata(new List<PointCollection>(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
        );

    /// <summary>
    /// Holes property
    /// </summary>
    public List<PointCollection> Holes
    {
        get
        {
            return (List<PointCollection>)GetValue(HolesProperty);
        }
        set
        {
            SetValue(HolesProperty, value);
        }
    }

    /// <summary>
    /// Points property
    /// </summary>
    public static readonly DependencyProperty PointsProperty = DependencyProperty.Register(
            "Points", typeof(PointCollection), typeof(PolygonWithHoles),
            new FrameworkPropertyMetadata(new PointCollection(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
        );

    /// <summary>
    /// Points property
    /// </summary>
    public PointCollection Points
    {
        get
        {
            return (PointCollection)GetValue(PointsProperty);
        }
        set
        {
            SetValue(PointsProperty, value);
        }
    }

    /// <summary>
    /// FillRule property
    /// </summary>
    public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register(
        "FillRule",
        typeof(FillRule),
        typeof(PolygonWithHoles),
        new FrameworkPropertyMetadata(
            FillRule.EvenOdd,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
        );

    /// <summary>
    /// FillRule property
    /// </summary>
    public FillRule FillRule
    {
        get
        {
            return (FillRule)GetValue(FillRuleProperty);
        }
        set
        {
            SetValue(FillRuleProperty, value);
        }
    }

    #endregion Dynamic Properties

    #region Protected Methods and properties

    /// <summary>
    /// Get the polygon that defines this shape
    /// </summary>
    protected override Geometry DefiningGeometry
    {
        get
        {
            return _polygonGeometry;
        }
    }

    #endregion

    #region Protected Methods

    /// <summary>
    /// Updates DesiredSize of the shape.  Called by parent UIElement during is the first pass of layout.
    /// </summary>
    /// <param name="constraint">Constraint size is an "upper limit" that should not exceed.</param>
    /// <returns>Shape's desired size.</returns>
    protected override Size MeasureOverride(Size constraint)
    {
        CacheDefiningGeometry();

        return base.MeasureOverride(constraint);
    }

    #endregion

    #region Internal Methods

    internal void CacheDefiningGeometry()
    {
        PointCollection pointCollection = Points;
        
        if (pointCollection == null)
        {
            _polygonGeometry = Geometry.Empty;
            return;
        }

        PathFigure pathFigure = new PathFigure();
        if (pointCollection.Count > 0)
        {
            pathFigure.StartPoint = pointCollection[0];

            if (pointCollection.Count > 1)
            {
                Point[] array = new Point[pointCollection.Count - 1];

                for (int i = 1; i < pointCollection.Count; i++)
                {
                    array[i - 1] = pointCollection[i];
                }

                pathFigure.Segments.Add(new PolyLineSegment(array, true));
            }

            pathFigure.IsClosed = true;
        }

        PathGeometry polygonGeometry = new PathGeometry();
        polygonGeometry.Figures.Add(pathFigure);
        polygonGeometry.FillRule = FillRule.Nonzero;

        GeometryGroup geometryGroup = new GeometryGroup();
        // Set FillRule
        geometryGroup.FillRule = FillRule;
        geometryGroup.Children.Add(polygonGeometry);

        // Holes
        List<PointCollection> holesCollection = Holes;
        if (holesCollection != null)
        {
            foreach (PointCollection holePointCollection in holesCollection)
            {
                PathFigure holePathFigure = new PathFigure();

                if (holePointCollection.Count > 0)
                {
                    holePathFigure.StartPoint = holePointCollection[0];

                    if (holePointCollection.Count > 1)
                    {
                        Point[] array = new Point[holePointCollection.Count - 1];

                        for (int i = 1; i < holePointCollection.Count; i++)
                        {
                            array[i - 1] = holePointCollection[i];
                        }

                        holePathFigure.Segments.Add(new PolyLineSegment(array, true));
                    }

                    holePathFigure.IsClosed = true;
                }

                PathGeometry holePolygonGeometry = new PathGeometry();
                holePolygonGeometry.Figures.Add(holePathFigure);
                holePolygonGeometry.FillRule = FillRule.Nonzero;

                geometryGroup.Children.Add(holePolygonGeometry);
            }

        }
        

        _polygonGeometry = geometryGroup;
    }

    #endregion Internal Methods

    #region Private Methods and Members

    private Geometry _polygonGeometry;

    #endregion
}
npetrovski
  • 181
  • 1
  • 7