using System; using System.Collections.Generic; /// /// The 2D polygon. /// author Roman Kushnarenko (sromku@gmail.com) /// C# version https://github.com/BergWerkGIS /// public class Polygon { private BoundingBox _boundingBox; private List _sides; private Polygon(List sides, BoundingBox boundingBox) { _sides = sides; _boundingBox = boundingBox; } /// /// Get the builder of the polygon /// public static Builder GetBuilder => new Builder(); /// /// Builder of the polygon /// public class Builder { private List _vertexes = new List(); private List _sides = new List(); private BoundingBox _boundingBox = null; private bool _firstPoint = true; private bool _isClosed = false; /// /// Add vertex points of the polygon. /// It is very important to add the vertexes by order, like you were drawing them one by one. /// /// The vertex point. /// The builder. public Builder addVertex(Point point) { if (_isClosed) { // each hole we start with the new array of vertex points _vertexes = new List(); _isClosed = false; } updateBoundingBox(point); _vertexes.Add(point); // add line (edge) to the polygon if (_vertexes.Count > 1) { Line Line = new Line(_vertexes[_vertexes.Count - 2], point); _sides.Add(Line); } return this; } /// /// Close the polygon shape. This will create a new side (edge) from the last vertex point to the first vertex point. /// /// The builder. public Builder close() { validate(); // add last Line _sides.Add(new Line(_vertexes[_vertexes.Count - 1], _vertexes[0])); _isClosed = true; return this; } /// /// Build the instance of the polygon shape. /// /// The polygon. public Polygon build() { validate(); // in case you forgot to close if (!_isClosed) { // add last Line _sides.Add(new Line(_vertexes[_vertexes.Count - 1], _vertexes[0])); } Polygon polygon = new Polygon(_sides, _boundingBox); return polygon; } /// /// Update bounding box with a new point. /// /// New point. private void updateBoundingBox(Point point) { if (_firstPoint) { _boundingBox = new BoundingBox(); _boundingBox.xMax = point.x; _boundingBox.xMin = point.x; _boundingBox.yMax = point.y; _boundingBox.yMin = point.y; _firstPoint = false; } else { // set bounding box if (point.x > _boundingBox.xMax) { _boundingBox.xMax = point.x; } else if (point.x < _boundingBox.xMin) { _boundingBox.xMin = point.x; } if (point.y > _boundingBox.yMax) { _boundingBox.yMax = point.y; } else if (point.y < _boundingBox.yMin) { _boundingBox.yMin = point.y; } } } private void validate() { if (_vertexes.Count < 3) { throw new Exception("Polygon must have at least 3 points"); } } } /// /// Check if the the given point is inside of the polygon. /// /// The point to check. /// True if the point is inside the polygon, otherwise return False. public bool contains(Point point) { if (inBoundingBox(point)) { Line ray = createRay(point); int intersection = 0; foreach (Line side in _sides) { if (intersect(ray, side)) { // System.out.println("intersection++"); intersection++; } } /* * If the number of intersections is odd, then the point is inside the polygon */ if (intersection % 2 != 0) { return true; } } return false; } public List getSides() { return _sides; } /// /// By given ray and one side of the polygon, check if both lines intersect. /// /// /// /// True if both lines intersect, otherwise return False. private bool intersect(Line ray, Line side) { Point intersectPoint = null; // if both vectors aren't from the kind of x=1 lines then go into if (!ray.isVertical() && !side.isVertical()) { // check if both vectors are parallel. If they are parallel then no intersection point will exist if (ray.getA() - side.getA() == 0) { return false; } double x = ((side.getB() - ray.getB()) / (ray.getA() - side.getA())); // x = (b2-b1)/(a1-a2) double y = side.getA() * x + side.getB(); // y = a2*x+b2 intersectPoint = new Point(x, y); } else if (ray.isVertical() && !side.isVertical()) { double x = ray.getStart().x; double y = side.getA() * x + side.getB(); intersectPoint = new Point(x, y); } else if (!ray.isVertical() && side.isVertical()) { double x = side.getStart().x; double y = ray.getA() * x + ray.getB(); intersectPoint = new Point(x, y); } else { return false; } // System.out.println("Ray: " + ray.toString() + " ,Side: " + side); // System.out.println("Intersect point: " + intersectPoint.toString()); if (side.isInside(intersectPoint) && ray.isInside(intersectPoint)) { return true; } return false; } /// /// Create a ray. The ray will be created by given point and on point outside of the polygon. /// he outside point is calculated automatically. /// /// /// private Line createRay(Point point) { // create outside point double epsilon = (_boundingBox.xMax - _boundingBox.xMin) / 10e6; Point outsidePoint = new Point(_boundingBox.xMin - epsilon, _boundingBox.yMin); Line vector = new Line(outsidePoint, point); return vector; } /// /// Check if the given point is in bounding box. /// /// /// True if the point in bounding box, otherwise return False private bool inBoundingBox(Point point) { if (point.x < _boundingBox.xMin || point.x > _boundingBox.xMax || point.y < _boundingBox.yMin || point.y > _boundingBox.yMax) { return false; } return true; } private class BoundingBox { public double xMax = double.NegativeInfinity; public double xMin = double.NegativeInfinity; public double yMax = double.NegativeInfinity; public double yMin = double.NegativeInfinity; } }