Multipart regions and containment#
A disconnected or perforated region is still one boundary, and containment against it is exact.
import numpy as np
import oowl
from oowl import boundaries as B
from oowl import layouts as L
from oowl import modifiers as M
Multipart regions and holes#
A MultiPolygon, from a Union in Boundary shapes or a partition or severed
setback in Boundary modifiers, is one farm. One turbine count is spread
across its parts, and no turbine is placed in a hole. The parts of a partition
are its cells in the order of Composite.parts, whether or not a gap separates
them.
An oowl.Cluster is different. Each member Farm keeps its own boundary,
turbines, resource and metadata, and modifiers apply per farm.
Farm.parts returns each part as a farm of its own, used here to count the
turbines in each part. On a partition farm.parts[i] is the farm in
farm.boundary.parts[i].
partitioned = B.Rectangle(2, 1, area=30e6).modify(
M.Partition.Channels(n_farms=3, gap=300, seed=1)
)
farm = oowl.Sampler(partitioned, L.PoissonDisc(60, seed=7)).sample()[0]
counts = [part.n_turbines for part in farm.parts]
farm.plot(
s=18,
title=f"one farm over {len(counts)} parts: N={farm.n_turbines}, split {counts}",
);
Containment#
Containment is exact and edge-inclusive, with no tolerance. A point on the edge is contained, but a perimeter point recomputed independently of the stored vertices may fall outside. The circle below is a 64-gon of the requested area, so its edge midpoints lie inside the nominal 1000 m radius and the nominal point beyond each midpoint falls outside.
Edge inclusion is not a clearance. For a margin use Setback from
Boundary modifiers.
circle = B.Circle(area=np.pi * 1000**2)
ring = np.asarray(circle.geometry.exterior.coords[:-1])
edge_midpoints = 0.5 * (ring + np.roll(ring, -1, axis=0))
nominal = 1000.0 * edge_midpoints / np.hypot(*edge_midpoints.T)[:, None]
radii = np.hypot(*ring.T)
print(f"stored vertices contained: {circle.contains(ring).all()}")
print(f"stored ring radius: {radii.min():.2f} m to {radii.max():.2f} m")
inside = int(circle.contains(nominal).sum())
print(f"nominal 1000 m points contained: {inside} of {len(nominal)}")
stored vertices contained: True
stored ring radius: 1000.80 m to 1000.80 m
nominal 1000 m points contained: 0 of 64