--- jupyter: description: 'Pick''s theorem with planex2d: a polygon with its corners on a square grid, the grid points inside it and on its boundary counted with Contains, its exact area against I + B/2 - 1, and the same polygon with two holes against I + B/2 + h - 1.' --- # Pick's theorem How can the area of a polygon be found by counting points? For a polygon whose corners lie on a square grid of points, Pick's theorem gives the area as $A = I + B/2 - 1$, from the number $I$ of grid points inside the polygon and the number $B$ of grid points on its boundary. No lengths, angles or coordinates enter the formula. For a polygon with $h$ holes, the area is $A = I + B/2 + h - 1$. The example draws a polygon on the grid, counts the grid points with `Contains` and compares the counts with the polygon's exact area, first as it is and then with two holes cut out. ![A polygon on a square grid, the grid points inside it and on its boundary](../_static/gallery/picks-theorem.png){.result} ```python import math import numpy as np import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## Counting the grid points `Contains` tests an array of points against a face at once. With `boundary=False`, it leaves out the points on the boundary, so the difference between the two counts is $B$: ```python def grid_points(face): """the grid points inside the face and those on its boundary""" (x0, y0), (x1, y1) = face.bounding_box grid = np.array([(x, y) for x in range(math.floor(x0), math.ceil(x1) + 1) for y in range(math.floor(y0), math.ceil(y1) + 1)], float) inside = face.Contains(grid, boundary=False) return grid[inside], grid[face.Contains(grid) & ~inside] ``` ## A polygon on the grid The example is a polygon with eight corners on the grid. Its area is compared with $I + B/2 - 1$ (Figure 1): ```python polygon = px.FromPolyline([(0, 0), (8, 1), (11, 6), (7, 5), (6, 10), (2, 7), (-2, 9), (1, 4)], closed=True).Face() inside, boundary = grid_points(polygon) I, B = len(inside), len(boundary) print("area", polygon.mass, "| I", I, "B", B, "| I + B/2 - 1", I + B / 2 - 1) ``` ```python nbsphinx="hide-input" Row((polygon, "I points inside and B on the boundary", {"dots": [tuple(p) for p in inside], "marks": [tuple(p) for p in boundary]}), caption="The grid points inside the polygon (dots) and on its boundary (crosses)") ``` ## Holes Each hole adds 1 to the formula. With two triangles cut out, both with their corners on the grid, the polygon has two holes, and the grid points on their edges count toward $B$. The area is compared with $I + B/2 + h - 1$ for $h = 2$ (Figure 2): ```python holes = [px.FromPolyline([(3, 2), (6, 2), (5, 4)], closed=True).Face(), px.FromPolyline([(2, 5), (4, 6), (3, 7)], closed=True).Face()] holed = polygon - holes[0] - holes[1] inside, boundary = grid_points(holed) I, B = len(inside), len(boundary) print("area", holed.mass, "| I", I, "B", B, "| I + B/2 + h - 1", I + B / 2 + len(holes) - 1) ``` ```python nbsphinx="hide-input" Row((holed, "Two holes", {"dots": [tuple(p) for p in inside], "marks": [tuple(p) for p in boundary]}), caption="The polygon with two holes and its grid points") ``` See [Inside or outside](../core/measuring/inside.md) for `Contains`. ```python nbsphinx="hidden" import sys import matplotlib.pyplot as plt sys.path.insert(0, "..") from docs_figures import FACE, INK, PLANEX, RED from gallery import save_figure inside, boundary = grid_points(polygon) fig, ax = plt.subplots(figsize=(6, 6)) xs, ys = np.meshgrid(range(-3, 13), range(-1, 12)) ax.scatter(xs, ys, s=6, color="#c3cbd3", zorder=1) polygon.Plot(ax=ax, face_col=FACE, line_col=INK, legend=False) ax.scatter(*inside.T, s=22, color=PLANEX, zorder=3) ax.scatter(*boundary.T, s=40, marker="x", color=RED, zorder=3) ax.set_aspect("equal") ax.axis("off") save_figure(fig, "picks-theorem") plt.close(fig) ```