Purpose
When a point-radius execution surface must guarantee that no part of a targeted cell is left unreached — a proximity alert, a store-visit radius, a coverage-complete media buy — the circumscribed circle is the correct construction. It trades the opposite direction from the inscribed circle: it never undershoots the cell, at the cost of claiming ground outside it and overlapping every neighboring cell's own circumscribed circle.
Source geometry and destination geometry
Source is an h3_cell_set, one circle computed per cell. Destination is
point_radius: a center latitude/longitude and a radius in meters, one
pair per input cell.
Definition
Center is the H3 cell center (cellToLatLng). Radius is the maximum
geodesic distance from that center to any point on the cell's true
boundary. For a hexagonal cell this maximum is attained exactly at a
vertex — unlike the inscribed case, using the vertex set here is directionally
correct, not a bug — but the implementation still densifies each edge with
great-circle samples before taking the maximum, because that same routine
must also be correct for distorted, non-regular, and pentagon cells where
the true maximum can, in principle, sit off-vertex under numerical
perturbation. A relative SAFETY_MARGIN = 1e-4 is added to the sampled
maximum so the guarantee holds even between samples and against
floating-point error in the underlying trig.
Robust algorithm
function circumscribedRadius(cell):
center = cellCenter(cell)
boundary = cellBoundary(cell) # great-circle arcs, vertex list
samples = densifyGreatCircle(boundary, perEdge=64)
maxDist = 0
for p in samples:
d = haversineDistance(center, p)
if d > maxDist: maxDist = d
return maxDist * (1 + 1e-4) # SAFETY_MARGIN growth
import { circumscribedCircle } from "@/lib/h3/circles";
const approx = circumscribedCircle(cell);
// approx.center: [lat, lng]
// approx.radiusMeters: maximum center->boundary distance, margin-grown
// approx.cellAreaM2, approx.circleAreaM2, approx.isPentagon
The same conversion with the Python bindings (h3-py v4):
import math
import h3
DEFAULT_EDGE_SAMPLES = 64
SAFETY_MARGIN = 1e-4
def _to_unit_vector(lat: float, lng: float):
lat_r, lng_r = math.radians(lat), math.radians(lng)
return (
math.cos(lat_r) * math.cos(lng_r),
math.cos(lat_r) * math.sin(lng_r),
math.sin(lat_r),
)
def _to_latlng(v):
x, y, z = v
return (math.degrees(math.asin(z)), math.degrees(math.atan2(y, x)))
def _slerp(a, b, t: float):
# Great-circle interpolation between two unit vectors — the same
# densify step used on the inscribed-circle page, reused here because
# the maximum, like the minimum, must be measured against the full
# densified boundary, not just the vertex list.
dot = max(-1.0, min(1.0, sum(ai * bi for ai, bi in zip(a, b))))
theta = math.acos(dot)
if theta == 0:
return a
sin_theta = math.sin(theta)
wa = math.sin((1 - t) * theta) / sin_theta
wb = math.sin(t * theta) / sin_theta
return tuple(wa * ai + wb * bi for ai, bi in zip(a, b))
def densify_edge_geodesic(a, b, samples: int = DEFAULT_EDGE_SAMPLES):
va, vb = _to_unit_vector(*a), _to_unit_vector(*b)
return [_to_latlng(_slerp(va, vb, i / samples)) for i in range(samples + 1)]
def circumscribed_radius_m(cell: str) -> float:
center = h3.cell_to_latlng(cell)
boundary = h3.cell_to_boundary(cell)
n = len(boundary)
max_dist = 0.0
for i in range(n):
a, b = boundary[i], boundary[(i + 1) % n]
for sample in densify_edge_geodesic(a, b):
d = h3.great_circle_distance(center, sample, unit="m")
if d > max_dist:
max_dist = d
return max_dist * (1 + SAFETY_MARGIN) # grow so the guarantee holds between samples
h3-py has no circumscribedCircle helper; this mirrors the same
densify-then-measure approach as the inscribed-circle page but takes the
maximum center-to-sample great_circle_distance instead of the
minimum, then grows it by the 1e-4 safety margin instead of shrinking
it. For a regular hexagon the true maximum lands on a vertex, but the
algorithm still densifies every edge rather than checking only the
vertex list, so it stays correct for pentagons and distorted cells too.
The tested reference implementation is the TypeScript in lib/.
Containment guarantee
Every point of the true H3 cell lies inside the circumscribed disk:
cell ⊆ circumscribed disk. This is the mirror-image guarantee of the
inscribed circle's ⊆ in the other direction, and it holds under the same
spherical model and the same 1e-4 safety margin against the finite-sample
approximation.
Resolution behavior
Radius scales with cell edge length, shrinking by roughly sqrt(7) ≈ 2.65×
per resolution step, same as the inscribed circle. The relative overreach
— overreach area as a fraction of cell area — is resolution-invariant for
regular hexagons, since it is a function of cell shape, not size; the
absolute duplicate-eligibility area shrinks with resolution even as the
relative figure holds steady.
Units and CRS
Center and boundary coordinates are [lat, lng] (h3-js native order),
EPSG:4326. Distances are spherical (haversine) meters on the same
R_MEAN = 6,371,008.8 m sphere cellArea uses.
Quality metrics
- overreach_ratio
- area(circumscribed disk − cell) / area(cell). For a regular hexagon this is approximately 0.209 — the disk is about 20.9% larger than the cell it circumscribes.
- duplicate_eligibility_area
- computed via duplicateEligibilityAreaM2(circles) — total area double-counted across a set of overlapping circumscribed circles: Σ area(disk_i) − area(union of disks). Non-zero whenever any two neighboring cells' circumscribed circles overlap, which is every adjacent pair by construction.
For a regular hexagon, circumradius equals the cell's own "radius" R
(center to vertex), so circleAreaM2 = π R² against a hexagon area of
(3√3/2) R² ≈ 2.598 R², giving a disk/cell area ratio of about 1.209 —
consistent with the cos(30°) ≈ 0.866 radius ratio between this
construction and the inscribed circle:
r_in / r_out = cos(30°) for a regular hexagon, so the two constructions
bound the true cell area from below (0.907× cell area) and above (1.209×
cell area) respectively, with the cell itself sitting strictly between.
Duplicate eligibility and experiment contamination
Because every circumscribed circle extends past its cell's true boundary,
adjacent circles overlap in a band along every shared edge. Any ground in
that band is eligible under two (or, near a hexagon corner, three) circles
at once. This is a measurement hazard, not merely a targeting inefficiency:
a device or household in an overlap band can be counted as reached by two
"different" cells in a report, inflating apparent reach and corrupting
per-cell frequency capping. duplicateEligibilityAreaM2 quantifies this
directly from the set of circle features actually used, rather than from a
theoretical hexagon — call it after generating the full circle set for a
campaign, not per-cell in isolation, since the metric is inherently a
property of the set.
If circumscribed circles are used to define treatment cells in a geo-lift test, the overlap band between a treatment cell and an adjacent control cell means the control cell is partially exposed to the treatment circle's radius. This leaks treatment into control and biases the measured lift downward. Use the inscribed circle, not this construction, whenever cells must remain mutually exclusive for measurement purposes.
Edge cases
Pentagons have five, shorter, less regular edges,
and their circumradius is measured relative to a smaller, less regular
cell area, so the overreach ratio at the 12 pentagon cells per resolution
deviates further from the regular-hexagon figure above — flag them
(isPentagon on the result) and do not average them into a fleet-wide
overreach estimate. Cells whose boundary crosses an icosahedron face seam
are still handled correctly by the distance-to-boundary maximum, since the
algorithm makes no planarity assumption.
Assumptions and limitations
The circumscribed circle guarantees coverage but never non-overlap;
treating a set of circumscribed circles as a partition (for exclusive
budget allocation, for instance) will overcount the ground in every overlap
band. Use duplicate_eligibility_area to size that overcounting before
committing a media plan or a measurement design to this construction, and
prefer the equal-area circle when the
requirement is area-representative reach rather than guaranteed coverage.
