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:
EnumThe 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:
objectA set of cells of one grid rdggs, given as index strings or
Cellobjects and kept as index strings, deduplicated and otherwise exactly as given. Iterating yieldsCellobjects;cellsis 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_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.distanceto a member is at most dist (planar distance with plane = True, geodesic with plane = False; seeCell.distancefor 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
- child(levels: int = 1, inherit_id: bool = False) ZoneSet[source]¶
The descendants of the members from one to levels levels down:
childof 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’smax_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).
- 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_overlapsis 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:
parentof 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')
- sibling(levels: int = 1, inherit_id: bool = False) ZoneSet[source]¶
The siblings of the members, themselves included:
siblingof 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 example40.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