Point in Polygon — Vertex Intersection Bug
Hotspots inside fire perimeter reported outside due to vertex intersection miscounts.
- Ray casting: count horizontal ray crossings — odd = inside, even = outside. O(n) per query.
- Winding number: compute signed angular sum — non-zero = inside. Handles self-intersecting polygons.
- Edge cases: ray through vertex or along edge can miscount. Handle with epsilon and orientation tests.
- Performance: O(n) for both. Use spatial index (grid, R-tree) for repeated queries against same polygon.
- Production gotcha: floating-point precision causes false results near boundaries. Use epsilon comparisons.
Given a polygon and a point, is the point inside? Ray casting: cast a ray from the point in any direction, count how many times it crosses the polygon boundary. Even = outside, odd = inside. It sounds simple but edge cases — rays passing exactly through vertices or along edges — require careful handling.
Point-in-polygon (PIP) is one of the most frequently implemented computational geometry algorithms. GIS systems (is this GPS coordinate in this city boundary?), game physics (is the player inside this room?), UI hit-testing (was this click inside this region?), and graphics rendering all need PIP. Two main algorithms: ray casting (even-odd rule, simple) and winding number (handles self-intersecting polygons, more robust).
Ray Casting Algorithm
The ray casting algorithm (even-odd rule) is the simplest and fastest method for simple polygons. Cast a horizontal ray from the point to +infinity in the x-direction. Count every edge that crosses this ray. If the count is odd, the point is inside; even means outside. The implementation uses a crossing test that checks if the edge's y-range spans the point's y-coordinate and if the intersection x of the edge with the ray is to the right of the point.
The crossing logic hides a subtlety: when the ray passes exactly through a vertex, the edge on one side may count the vertex as a crossing while the adjacent edge may not. The standard fix is to treat the vertex as belonging to the edge with the higher y-coordinate (strict upper vertex rule). This ensures the crossing count remains correct even for vertex-aligned rays.
Winding Number Algorithm
The winding number algorithm computes how many times the polygon winds around the point. It does this by summing signed angles or, more efficiently, by incrementing/decrementing a counter when edges cross a reference line. The result is an integer: zero means outside, non-zero means inside. The winding number is more robust than ray casting because it handles self-intersecting polygons (like star shapes) correctly.
Implementation: start with wn = 0. For each edge, check if the point's y-coordinate is between the edge's y's. If the edge crosses above the point (upward crossing), increment wn; if it crosses below (downward crossing), decrement. The orientation (left turn or right turn) determines the sign. At the end, wn = 0 means outside, |wn| > 0 means inside.
Edge Cases in Ray Casting — Vertex and Edge Alignments
The most common production failures in ray casting stem from numerical instability when the ray passes exactly through a vertex or lies collinear with an edge. A vertex on the ray gets counted twice if both adjacent edges are considered crossing. The standard solution: treat the vertex as belonging only to the edge with the higher y-coordinate (or lower, as long as consistent).
Similarly, when the ray overlaps an edge, the algorithm must not count that edge as a crossing. The crossing test naturally excludes horizontal edges because the condition '(yi > y) != (yj > y)' is false when yi == yj == y. But floating-point equality can fail. Use an epsilon tolerance: if abs(y - yi) < EPS and abs(y - yj) < EPS, skip the edge.
Another edge case: the point lies exactly on an edge. Both algorithms are undefined — is the point inside, outside, or on the boundary? Application-specific rules decide. In GIS, a point on a boundary is often considered inside.
Performance Optimisation for Multiple Queries
When you need to test thousands or millions of points against the same polygon, the O(n) per query becomes too slow. Preprocessing the polygon with a spatial index reduces average query complexity to O(log n) or O(1). Common approaches:
- Grid subdivision: Divide the polygon's bounding box into a grid. For each cell, store which edges cross that cell. A point's query first checks which grid cell it falls in, then tests only edges in that cell.
- Slab decomposition: For convex polygons, sort vertices by angle around a point (e.g., centroid) and binary search to find which wedge the point falls into, then test against that edge. O(log n) per query.
- R-tree: Build an R-tree over polygon edges. Query the point's location — the tree returns candidate edges that intersect the point's horizontal ray. Works well for complex polygons with many edges.
For vectorised batch queries, use NumPy to compute crossings for all points simultaneously. This leverages SIMD operations and can process millions of points per second against a single polygon.
Choosing Between Ray Casting and Winding Number
Both algorithms run in O(n) time and O(1) memory for a single query. The choice depends on polygon properties and numerical requirements.
Ray casting pros: Simple, fast, works well for convex and simple concave polygons. Easy to vectorise. Fewer floating-point operations per edge. Ray casting cons: Fails on self-intersecting polygons. Vertex intersections require careful handling. Does not distinguish between winding number magnitude (useful for some applications).
Winding number pros: Handles any polygon type, including self-intersecting. Produces an integer that indicates how many times the polygon winds around the point (useful for regions with holes or nested boundaries). More consistent near boundaries. Winding number cons: Slightly more complex — requires orientation test. Harder to vectorise (though possible). Slightly more arithmetic per edge.
In practice, most production systems use winding number because real-world polygon data is messy. For performance-critical applications with clean convex polygons, ray casting with proper edge case handling is the better choice.
- Ray crossing is intuitive for simple shapes but breaks on self-intersecting fences.
- Winding number is like counting the net number of times the fence loops around you.
- Self-intersecting polygons create overlapping 'fence loops' — ray casting miscounts because one crossing can be part of two loops.
- In production, treat every polygon as potentially self-intersecting until proven otherwise.
Wildfire Perimeter Detection Failed — Vertex Intersection Bug
- Vertex intersections are not edge cases — they will happen in production with real-world polygons.
- Use the 'strict upper vertex' rule: count crossing only if the vertex with greater y is the start point of the edge.
- Always test with concave polygons and vertex-aligned rays, not just convex shapes.
Key takeaways
Common mistakes to avoid
4 patternsUsing ray casting on self-intersecting polygons without fallback
Ignoring floating-point precision in crossing calculation
Forgetting to close the polygon (last vertex equals first)
Not handling the case where the point lies exactly on an edge or vertex
Interview Questions on This Topic
Explain the ray casting algorithm for point-in-polygon and describe how you handle the case when the ray passes through a vertex.
Frequently Asked Questions
That's Geometry. Mark it forged?
4 min read · try the examples if you haven't