Zone Sets

OGC Topic 21 v2.0 defines its zone queries (Table 53) between zonal identifiers, and several of them return or compare sets of zones: the set operations union, intersection, difference and symDifference, buffer, the hierarchy walks parent, child and sibling, and the attributes boundary, boundaryType and convexHull of “the combined spatial geometries of the zones in the query”. ZoneSet is that set of zones, and Cell has the same methods for the zone-to-zone form, each returning a ZoneSet. The DE-9IM predicates, distance and the one-dimensional relations are described on the The cell Module page.

A set of cells

A ZoneSet holds cells of one grid at any resolutions, exactly as given: index strings, Cell objects or the array polyfill_array returns. Nested members are allowed, because the spec’s own hierarchy results contain them:

>>> from rhealpixdggs.dggs import WGS84_003
>>> from rhealpixdggs.zoneset import ZoneSet
>>> zs = ZoneSet(WGS84_003, ['P40', 'P41', 'P43'])
>>> zs.cells, len(zs), zs.resolutions
(('P40', 'P41', 'P43'), 3, (2,))
>>> [str(c) for c in zs][:2], 'P41' in zs
(['P40', 'P41'], True)

Hierarchy

parent, child and sibling take the spec’s levels and inheritID arguments (clause 8.3.3). The worked example there, for a zone 40, reads as follows for the cell P40:

>>> p40 = WGS84_003.cell(['P', 4, 0])
>>> p40.parent().cells
('P4',)
>>> WGS84_003.cell(['P', 4, 0, 0]).parent(levels=2).cells
('P4', 'P40')
>>> sorted(p40.sibling(inherit_id=True).cells)
['P40', 'P41', 'P42', 'P43', 'P44', 'P45', 'P46', 'P47', 'P48']
>>> len(p40.sibling())   # inheritance ignored: the cell and its 8 neighbours
9
>>> len(WGS84_003.cell(['P', 4]).child(levels=2))   # children and grandchildren
90

With inheritID True, siblings share a parent; with it False, the default, inheritance is ignored and the siblings of a cell are the cells at its resolution within levels edge or corner steps, its k-ring, as the spec’s 40.sibling() = {13, 22, 23, 31, 40, 41, 42, 43} shows. For parent and child the flag changes nothing, since every rHEALPix cell’s identifier extends its parent’s. The Boolean forms are parent_of(), child_of() and sibling_of():

>>> p32 = WGS84_003.cell(['P', 3, 2])   # left of P40, in the next parent
>>> p40.sibling_of(p32), p40.sibling_of(p32, inherit_id=True)
(True, False)

Set operations

The set operations treat a ZoneSet as the region its cells cover and return disjoint cells. They are exact on the index strings: the intersection of two nested cells is the finer one, and a difference refines a cell down to the resolution of what is removed from it:

>>> p4 = ZoneSet(WGS84_003, ['P4'])
>>> p4.intersection(ZoneSet(WGS84_003, ['P40', 'P5'])).cells
('P40',)
>>> p4.difference(ZoneSet(WGS84_003, ['P40'])).cells
('P41', 'P42', 'P43', 'P44', 'P45', 'P46', 'P47', 'P48')

The spec’s rangeRefine argument, here min_res and max_res, filters the result to a range of resolutions, both ends included; its default is the range spanned by the two operands. Within the range, complete groups of siblings are compacted to their parent, but never coarser than min_res:

>>> eight = p4.difference(ZoneSet(WGS84_003, ['P40']))
>>> len(eight.union(ZoneSet(WGS84_003, ['P40'])))   # default range: resolution 2
9
>>> eight.union(ZoneSet(WGS84_003, ['P40']), min_res=1).cells
('P4',)

buffer grows a set by a distance, using distance(): every member, and every cell at a member’s resolution whose distance to it is at most dist, in the plane or on the ellipsoid:

>>> len(WGS84_003.cell(['P', 4, 4]).buffer(0))   # touching cells are at distance 0
9

Relations between sets

The DE-9IM questions the The cell Module page answers for two cells are also answered between two sets, read as the regions their cells cover: equals, contains, within, intersects, disjoint, touches, overlaps and crosses. Unlike two single cells, two sets can genuinely overlap, sharing some ground while each has ground of its own; Cell.region_overlaps is the one-cell case of that:

>>> a = ZoneSet(WGS84_003, ['P40', 'P41'])
>>> a.overlaps(ZoneSet(WGS84_003, ['P41', 'P42']))
True
>>> a.touches(ZoneSet(WGS84_003, ['P42'])), a.within(ZoneSet(WGS84_003, ['P4']))
(True, True)
>>> ZoneSet(WGS84_003, [f'P4{d}' for d in range(9)]).equals(ZoneSet(WGS84_003, ['P4']))
True

Geometry of a query

The combined geometry of the set, its boundary and its convex hull come from the export geometries (Export), so they are shapely objects in longitude-latitude degrees with the antimeridian and pole conventions of that page, and the convex hull is a planar hull of that map. The boundaryType attribute is a value of the EA_BoundaryType code list, BoundaryType: every rHEALPix cell edge is a straight line in the equal-area plane, PROJECTED_LINE, and the edges of equatorial cells are also parallels and meridians:

>>> zs.geometry.geom_type, zs.boundary.geom_type
('Polygon', 'LineString')
>>> zs.boundary_type.value
'projectedLine'

Module reference

Sets of cells, and the zone-set operations of OGC Topic 21 v2.0 Table 53.

A ZoneSet holds cells of one grid, at any resolutions, exactly as given: the spec’s hierarchy operations return sets such as {40, 4} whose members nest. The set operations union, intersection, difference and sym_difference treat a set as the region its cells cover and return disjoint cells: exact hierarchical algebra on the index strings (the intersection of two nested cells is the finer one; a difference refines a cell down to the resolution of what is removed from it), then the spec’s rangeRefine filter on resolution, with complete sibling groups compacted no coarser than the range’s lower bound. buffer grows a set by a distance; parent, child and sibling walk the hierarchy by levels with the inheritID flag of clause 8.3.3; and geometry, boundary, convex_hull and boundary_type are the attributes of the query’s combined geometry.

class rhealpixdggs.zoneset.BoundaryType(*values)[source]

Bases: Enum

The EA_BoundaryType code list of OGC Topic 21 v2.0 Table 76: the kinds of curve an equal-area cell boundary may be built from. Every edge of an rHEALPix cell is a straight line in the equal-area plane, which is PROJECTED_LINE; the edges of equatorial cells are also parallels (SMALL_CIRCLE) and meridians (GREAT_ELLIPSE).

GEODESIC = 'geodesic'
GREAT_CIRCLE = 'greatCircle'
GREAT_ELLIPSE = 'greatEllipse'
PROJECTED_LINE = 'projectedLine'
SMALL_CIRCLE = 'smallCircle'
SMALL_ELLIPSE = 'smallEllipse'
class rhealpixdggs.zoneset.ZoneSet(rdggs: RHEALPixDGGS, cells: Iterable[str | Cell] = ())[source]

Bases: object

A set of cells of one grid rdggs, given as index strings or Cell objects and kept as index strings, deduplicated and otherwise exactly as given. Iterating yields Cell objects; cells is the sorted tuple of index strings. Raise ValueError for an invalid index or a cell of another grid.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> zs = ZoneSet(WGS84_003, ['P41', 'P40', 'P4'])
>>> zs.cells, len(zs), 'P40' in zs, zs.resolutions
(('P4', 'P40', 'P41'), 3, True, (1, 2))
property boundary: Any

The boundary of the combined geometry (Table 53’s boundary).

property boundary_type: BoundaryType

Table 53’s boundaryType: the kind of curve the boundary is made of. Every rHEALPix cell edge is a straight line in the equal-area plane, BoundaryType.PROJECTED_LINE.

buffer(dist: float, plane: bool = True, n: int = 8) → ZoneSet[source]

The cells at each member’s resolution within dist of the set: every member, and every cell whose Cell.distance to a member is at most dist (planar distance with plane = True, geodesic with plane = False; see Cell.distance for units and n). With dist 0 that is the members and everything touching them.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> len(ZoneSet(WGS84_003, ['P44']).buffer(0))
9
property cells: tuple[str, ...]

The index strings, sorted.

child(levels: int = 1, inherit_id: bool = False) → ZoneSet[source]

The descendants of the members from one to levels levels down: child of Table 53, including every intermediate level as the spec’s examples do. inherit_id changes nothing in a nested hierarchy. Raise ValueError if a level would exceed the grid’s max_resolution.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> len(ZoneSet(WGS84_003, ['P4']).child(levels=2))
90
contains(other: Cell | ZoneSet) → bool[source]

DE-9IM contains between regions: True if the region other covers lies within the region this set covers. As for cells, this coincides with covers, since the regions are closed and tile.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> ZoneSet(WGS84_003, ['P4']).contains(ZoneSet(WGS84_003, ['P40', 'P41']))
True
property convex_hull: Any

The convex hull of the combined geometry in longitude-latitude coordinates (Table 53’s convexHull); a set split by the antimeridian hulls across the map, so this is a planar notion.

crosses(other: Cell | ZoneSet) → bool[source]

DE-9IM crosses between regions: always False, as both are two-dimensional; present because the interface mandates it.

difference(other: Cell | ZoneSet, min_res: int | None = None, max_res: int | None = None) → ZoneSet[source]

The region covered by this set but not by other: a cell partly removed is refined to the resolution of the cells removed from it. Range and compaction as for union.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> ZoneSet(WGS84_003, ['P4']).difference(ZoneSet(WGS84_003, ['P40'])).cells
('P41', 'P42', 'P43', 'P44', 'P45', 'P46', 'P47', 'P48')
disjoint(other: Cell | ZoneSet) → bool[source]

DE-9IM disjoint between regions: the negation of intersects.

equals(other: Cell | ZoneSet) → bool[source]

DE-9IM equals between the regions this set and other cover: True if neither covers ground the other does not, however the cells are cut (nine children equal their parent).

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> children = [f'P4{d}' for d in range(9)]
>>> ZoneSet(WGS84_003, children).equals(ZoneSet(WGS84_003, ['P4']))
True
property geometries: Any

The members’ geometries in longitude-latitude degrees (export.geometries).

property geometry: Any

The union of the members’ geometries: the query’s combined geometry.

intersection(other: Cell | ZoneSet, min_res: int | None = None, max_res: int | None = None) → ZoneSet[source]

The region covered by both this set and other: for two nested cells the finer one. Range and compaction as for union.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> ZoneSet(WGS84_003, ['P4']).intersection(ZoneSet(WGS84_003, ['P40', 'P5'])).cells
('P40',)
intersects(other: Cell | ZoneSet) → bool[source]

DE-9IM intersects between regions: True if they share a point, that is share interior or have a pair of cells that touch.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> ZoneSet(WGS84_003, ['P40']).intersects(ZoneSet(WGS84_003, ['P41']))
True
overlaps(other: Cell | ZoneSet) → bool[source]

DE-9IM overlaps between regions: True if they share interior and each covers ground the other does not. Unlike two single cells, two sets of cells can overlap; Cell.region_overlaps is the one-cell case.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> a = ZoneSet(WGS84_003, ['P40', 'P41'])
>>> a.overlaps(ZoneSet(WGS84_003, ['P41', 'P42']))
True
>>> a.overlaps(ZoneSet(WGS84_003, ['P4']))
False
parent(levels: int = 1, inherit_id: bool = False) → ZoneSet[source]

The ancestors of the members from one to levels levels up, as many as exist: parent of Table 53. inherit_id is accepted for the spec’s signature; in rHEALPix’s nested hierarchy every ancestor shares its descendants’ identifier prefix, so it changes nothing.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> ZoneSet(WGS84_003, ['P400']).parent(levels=2).cells
('P4', 'P40')
property resolutions: tuple[int, ...]

The distinct resolutions present, ascending.

sibling(levels: int = 1, inherit_id: bool = False) → ZoneSet[source]

The siblings of the members, themselves included: sibling of Table 53. With inherit_id True, the cells at a member’s resolution under its ancestor levels levels up (its parent’s children for levels 1); with inherit_id False, inheritance is ignored and the siblings are the cells within levels edge or corner steps at the same resolution, the k-ring of that radius, as the spec’s example 40.sibling() = {13, 22, 23, 31, 40, 41, 42, 43} shows.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> len(ZoneSet(WGS84_003, ['P40']).sibling(inherit_id=True))
9
>>> len(ZoneSet(WGS84_003, ['P40']).sibling())
9
>>> len(ZoneSet(WGS84_003, ['P44']).sibling(levels=2))
25
sym_difference(other: Cell | ZoneSet, min_res: int | None = None, max_res: int | None = None) → ZoneSet[source]

The region covered by exactly one of this set and other. Range and compaction as for union.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> ZoneSet(WGS84_003, ['P4']).sym_difference(ZoneSet(WGS84_003, ['P4', 'P5'])).cells
('P5',)
touches(other: Cell | ZoneSet) → bool[source]

DE-9IM touches between regions: True if they share boundary points but no interior.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> a = ZoneSet(WGS84_003, ['P40', 'P41'])
>>> a.touches(ZoneSet(WGS84_003, ['P42'])), a.touches(ZoneSet(WGS84_003, ['P4']))
(True, False)
union(other: Cell | ZoneSet, min_res: int | None = None, max_res: int | None = None) → ZoneSet[source]

The region covered by this set or other, as disjoint cells within the resolution range min_res to max_res (default: the operands’ own range), compacted no coarser than min_res.

EXAMPLES:

>>> from rhealpixdggs.dggs import WGS84_003
>>> eight = ZoneSet(WGS84_003, [f'P4{d}' for d in range(8)])
>>> eight.union(ZoneSet(WGS84_003, ['P48']), min_res=1).cells
('P4',)
>>> len(eight.union(ZoneSet(WGS84_003, ['P48'])))  # default range: resolution 2
9
within(other: Cell | ZoneSet) → bool[source]

DE-9IM within between regions: the converse of contains.