The dggs Module

This Python 3.11 module implements the rHEALPix discrete global grid system.

  • Alexander Raichev (AR), 2012-11-12: Initial version based upon grids.py.

NOTES:

All lengths are measured in meters and all angles are measured in radians unless indicated otherwise.

By ‘ellipsoid’ throughout, I mean an ellipsoid of revolution and not a general (triaxial) ellipsoid.

Points lying on the plane are given in rectangular (horizontal, vertical) coordinates, and points lying on the ellipsoid are given in geodetic (longitude, latitude) coordinates unless indicated otherwise.

DGGS abbreviates ‘discrete global grid system’.

Except when manipulating positive integers, I avoid the modulo function ‘%’ and insted write everything in terms of ‘floor()’. This is because Python interprets the sign of ‘%’ differently than Java or C, and I don’t want to confuse people who are translating this code to those languages.

EXAMPLES:

Create the (1, 2)-rHEALPix DGGS with N_side = 3 that is based on the WGS84 ellipsoid. Use degrees instead of the default radians for angular measurements

>>> from rhealpixdggs.ellipsoids import WGS84_ELLIPSOID
>>> E = WGS84_ELLIPSOID
>>> rdggs = RHEALPixDGGS(ellipsoid=E, north_square=1, south_square=2, N_side=3)
>>> print(rdggs)
rHEALPix DGGS:
    N_side = 3
    north_square = 1
    south_square = 2
    max_areal_resolution = 1
    max_resolution = 15
    ellipsoid:
        R_A = 6371007.180918476
        a = 6378137.0
        b = 6356752.314245179
        e = 0.08181919084262149
        f = 0.0033528106647474805
        lat_0 = 0
        lon_0 = 0
        radians = False
        sphere = False

Pick a (longitude-latitude) point on the ellipsoid and find the resolution 1 cell that contains it

>>> p = (0, 45)
>>> c = rdggs.cell_from_point(1, p, plane=False); print(c)
N8

Find the ellipsoidal (edge) neighbors of this cell

>>> for (direction, cell) in sorted(c.neighbors(plane=False).items()):
...     print(direction, cell)
east N5
south_east Q0
south_west P2
west N7

Find the planar (edge) neighbors of this cell

>>> for (direction, cell) in sorted(c.neighbors('plane').items()):
...     print(direction, cell)
down P2
left N7
right Q0
up N5

Find all the resolution 1 cells intersecting the longitude-latitude aligned ellipsoidal quadrangle with given northwest and southeast corners

>>> nw = (0, 45)
>>> se = (90, 0)
>>> cells = rdggs.cells_from_region(1, nw, se, plane=False)
>>> for row in cells:
...     print([str(cell) for cell in row])
['N8', 'N5', 'N2']
['Q0', 'Q1', 'Q2', 'R0']
['Q3', 'Q4', 'Q5', 'R3']

Compute the ellipsoidal nuclei of these cells

>>> expected_results = [
...    [
...        (1.90833280887811e-14, 58.52801748206219),
...        (45.00000000000002, 58.52801748206219),
...        (89.99999999999997, 58.52801748206219)
...    ], [
...        (14.999999999999998, 26.490118751439734),
...        (45.0, 26.490118751439734),
...        (74.99999999999999, 26.490118751439734),
...        (105.00000000000001, 26.490118751439734)
...    ], [
...        (14.999999999999998, 0),
...        (45.0, 0),
...        (74.99999999999999, 0),
...        (105.00000000000001, 0)
...    ]]
>>> for i, row in enumerate(cells):
...     for j, cell in enumerate(row):
...         print(cell, assert_allclose(cell.nucleus(plane=False), expected_results[i][j], rtol=1e-15, atol=0) == None)
N8 True
N5 True
N2 True
Q0 True
Q1 True
Q2 True
R0 True
Q3 True
Q4 True
Q5 True
R3 True

Create a (0, 0)-rHEALPix DGGS with N_side = 3 based on the WGS84 ellipsoid. Use degrees instead of the default radians for angular measurements and orient the DGGS so that the planar origin (0, 0) is on Auckland, New Zealand

>>> p = (174, -37)  # Approximate Auckland lon-lat coordinates
>>> from rhealpixdggs.ellipsoids import *
>>> E = Ellipsoid(a=WGS84_A, f=WGS84_F, radians=False, lon_0=p[0], lat_0=p[1])
>>> rdggs = RHEALPixDGGS(E, N_side=3, north_square=0, south_square=0)
>>> print(rdggs)
rHEALPix DGGS:
    N_side = 3
    north_square = 0
    south_square = 0
    max_areal_resolution = 1
    max_resolution = 15
    ellipsoid:
        R_A = 6371007.180918476
        a = 6378137.0
        b = 6356752.314245179
        e = 0.08181919084262149
        f = 0.0033528106647474805
        lat_0 = -37
        lon_0 = 174
        radians = False
        sphere = False

>>> print(rdggs.cell_from_point(1, p, plane=False))
Q3
class rhealpixdggs.dggs.RHEALPixDGGS(ellipsoid=<rhealpixdggs.ellipsoids.Ellipsoid object>, N_side=3, north_square=0, south_square=0, max_areal_resolution=1)

Bases: object

Represents an rHEALPix DGGS on a given ellipsoid.

INSTANCE ATTRIBUTES:

  • ellipsoid - The underlying ellipsoid (Ellipsoid instance).

  • N_side - An integer of size at least 2. Each planar cell has N_side x N_side child cells.

  • (north_square, south_square) - Integers between 0 and 3 indicating the positions of north polar and south polar squares, respectively, of the rHEALPix projection used.

  • max_areal_resolution - An area measured in square meters that upper bounds the area of the smallest ellipsoidal grid cells.

  • max_resolution - A nonnegative integer that is the maximum grid resolution needed to have ellipsoidal cells of area at most max_areal_resolution.

  • child_order - A dictionary of the ordering (Morton order) of child cells of a cell in terms of the row-column coordinates in the matrix of child cells. Child cell are numbered 0 to N_side**2 -1 from left to right and top to bottom.

  • ul_vertex - A dictionary with key-value pairs (c, (x, y)), where c is an element of CELLS0 and (x, y) is the upper left corner point of the resolution 0 planar cell c.

  • atomic_neighbors - A dictionary with key-value pairs (n, {‘up’: a, ‘down’: b, ‘left’: c, ‘right’: d}), where n, a, b, c, and d are elements of CELLS0 or {0, 1, …, N_side**2 -1}. Describes the planar (edge) neighbors of cell0 letter / child cell number n.

NOTE:

Several RHEALPixDGGS methods have the keyword argument ‘plane’. Setting it to True indicates that all input and output points and cells are interpreted as lying in the planar DGGS. Setting it to False indicates that they are interpreted as lying in the ellipsoidal DGGS.

cell(suid=None, level_order_index=None, post_order_index=None)

Return a cell (Cell instance) of this DGGS either from its ID or from its resolution and index.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> c = rdggs.cell(('N', 4, 5))
>>> print(isinstance(c, Cell))
True
>>> print(c)
N45
cell_area(resolution, plane=True)

Return the area of a planar or ellipsoidal cell at the given resolution.

EXAMPLES:

>>> rdggs = UNIT_003
>>> a = rdggs.cell_area(1)
>>> print(a == (pi/6)**2)
True
>>> print(rdggs.cell_area(1, plane=False) == 8/(3*pi)*a)
True
cell_from_point(resolution, p, plane=True)

Return the resolution resolution cell that contains the point p. If plane = True, then p and the output cell lie in the planar DGGS. Otherwise, p and the output cell lie in the ellipsoidal DGGS.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> p = (0, 0)
>>> c = rdggs.cell_from_point(1, p)
>>> print(c)
Q3
>>> rdggs = RHEALPixDGGS(N_side=15)
>>> p = (80, -20)
>>> c = rdggs.cell_from_point(1, p, plane=False)
>>> print(c)
(Q, 178)
cell_from_region(ul, dr, plane=True)

Return the smallest planar or ellipsoidal cell wholly containing the region bounded by the axis-aligned rectangle with upper left and lower right vertices given by the the points ul and dr, respectively. If such as cell does not exist, then return None. If plane = True, then ul and dr and the returned cell lie in the planar DGGS. Otherwise, ul and dr and the returned cell lie in the ellipsoidal DGGS.

To specify an ellipsoidal cap region, set ul = (-pi, pi/2) and dr = (-pi, phi) for a northern cap from latitudes pi/2 to phi, or set ul = (-pi, phi) and dr = (-pi, -pi/2) for a southern cap from latitudes phi to -pi/2. (As usual, if self.ellipsoid.radians = False, then use degrees instead of radians when specifying ul and dr.)

EXAMPLES:

>>> rdggs = UNIT_003
>>> p = (0, pi/12)
>>> q = (pi/6 - 1e-6, 0)
>>> c = rdggs.cell_from_region(p, q)
>>> print(c)
Q3
cell_latitudes(resolution, phi_min, phi_max, nucleus=True, plane=True)

Return a list of every latitude phi whose parallel intersects a resolution resolution cell nucleus and satisfies phi_min < phi < phi_max. If plane = True, then use rHEALPix y-coordinates for phi_min, phi_max, and the result. Return the list in increasing order. If nucleus = False, then return a list of every latitude phi whose parallel intersects the north or south boundary of a resolution resolution cell and that satisfies phi_min < phi < phi_max.

NOTE:

By convention, the pole latitudes pi/2 and -pi/2 (or their corresponding rHEALPix y-coordinates) will be excluded.

There are 2*self.N_side**resolution - 1 nuclei latitudes between the poles if self.N_side is odd and 2*self.N_side**resolution if self.N_side is even. Consequently, there are 2*self.N_side**resolution boundary latitudes between the poles if self.N_side is odd and 2*self.N_side**resolution - 1 boundary latitudes if self.N_side is even.

EXAMPLES:

>>> rdggs = WGS84_003_RADIANS
>>> for phi in rdggs.cell_latitudes(1, -pi/2, pi/2, plane=False):
...     print(my_round(phi, 14))
-1.02150660972679
-0.46233979145691
0.0
0.46233979145691
1.02150660972679

>>> for phi in rdggs.cell_latitudes(1, -pi/2, pi/2, nucleus=False, plane=False):
...     print(my_round(phi, 14))
-1.29894395947616
-0.73195363195267
-0.22506566919844
0.22506566919844
0.73195363195267
1.29894395947616
cell_width(resolution, plane=True)

Return the width of a planar cell at the given resolution. If plane = False, then return None, because the ellipsoidal cells don’t have constant width.

EXAMPLES:

>>> rdggs = UNIT_003
>>> print(rdggs.cell_width(0) == pi/2)
True
>>> print(rdggs.cell_width(1) == pi/6)
True
cells_from_meridian(resolution, lam, phi_min, phi_max)

Return a list of the resolution resolution cells that intersect the meridian segment of longitude lam whose least latitude is phi_min and whose greatest latitude is phi_max. Sort the cells from north to south and west to east in case two cells with the same nucleus latitude intersect the meridian.

EXAMPLES:

>>> rdggs = WGS84_003_RADIANS
>>> cells = rdggs.cells_from_meridian(1, 0.1, -pi/2, pi/2)
>>> print([str(cell) for cell in cells])
['N4', 'N2', 'N1', 'Q0', 'Q3', 'Q6', 'S8', 'S7', 'S4']
cells_from_parallel(resolution, phi, lam_min, lam_max)

Return a list of the resolution resolution cells that intersect the parallel segment of latitude phi whose least longitude is lam_min and whose greatest longitude is lam_max. Sort the list from west to east.

EXAMPLES:

>>> rdggs = WGS84_003_RADIANS
>>> cells = rdggs.cells_from_parallel(1, pi/3, -pi, pi)
>>> print([str(cell) for cell in cells])
['N6', 'N7', 'N8', 'N5', 'N2', 'N1', 'N0', 'N3']
cells_from_region(resolution, ul, dr, plane=True)

If plane = True, then return a list of lists of resolution resolution cells that cover the axis-aligned rectangle whose upper left and lower right vertices are the points ul and dr, respectively. In the output, sort each sublist of cells from left to right (in the planar DGGS) and sort the sublists from top to bottom.

If plane = False, then return a list of lists of resolution resolution cells that cover the longitude-latitude aligned ellipsoidal quadrangle whose northwest and southeast vertices are the points ul and dr, respectively. Defunct quads with ul = (stuff, pi/2) or dr = (stuff, -pi/2) also work (and rely on the fact that the north and south pole can both be specified by infinitely many longitudes).

To specify an ellipsoidal cap region, set ul = (-pi, pi/2) and dr = (-pi, phi) for a northern cap from latitudes pi/2 to phi, or set ul = (-pi, phi) and dr = (-pi, -pi/2) for a southern cap from latitudes phi to -pi/2. (As usual, if self.ellipsoid.radians = False, then use degrees instead of radians when specifying ul and dr.)

In the output, sort each sublist of cells from west to east (in the ellipsoidal DGGS) and sort the sublists from north to south.

Return the empty list if if ul[0] > dr[0] or ul[1] < dr[1].

NOTE:

If plane = True, then the resulting list is a matrix, that is, each sublist has the same length. This is not necessarily so if plane = False; see the examples below.

EXAMPLES:

>>> rdggs = WGS84_003_RADIANS
>>> R_A = rdggs.ellipsoid.R_A
>>> ul = R_A*array((-0.1, pi/4))
>>> dr = R_A*array((0.1, -pi/4))  # Rectangle
>>> M = rdggs.cells_from_region(1, ul, dr)
>>> for row in M:
...     print([str(cell) for cell in row])
['P2', 'Q0']
['P5', 'Q3']
['P8', 'Q6']

>>> ul = (0, pi/3)
>>> dr = (pi/2, 0)  # Quad
>>> M = rdggs.cells_from_region(1, ul, dr, plane=False)
>>> for row in M:
...     print([str(cell) for cell in row])
['N2', 'N1', 'N0']
['Q0', 'Q1', 'Q2', 'R0']
['Q3', 'Q4', 'Q5', 'R3']

>>> ul = (0, -pi/6)
>>> dr = (pi/2, -pi/2)  # Defunct quad / lune segment
>>> M = rdggs.cells_from_region(1, ul, dr, plane=False)
>>> for row in M:
...     print([str(cell) for cell in row])
['Q6', 'Q7', 'Q8', 'R6']
['S8', 'S7', 'S6']
['S4']

>>> ul = (-pi, -pi/5)
>>> dr = (-pi, -pi/2)  # Cap
>>> M = rdggs.cells_from_region(1, ul, dr, plane=False)
>>> for row in M:
...     print([str(cell) for cell in row])
['O6', 'O7', 'O8', 'P6', 'P7', 'P8', 'Q6', 'Q7', 'Q8', 'R6', 'R7', 'R8']
['S0', 'S1', 'S2', 'S5', 'S8', 'S7', 'S6', 'S3']
['S4']
combine_triangles(u, v, inverse=False, region='none')

Return the combine_triangles() transformation of the point (u, v) (or its inverse if inverse = True) appropriate to the underlying ellipsoid. It maps the HEALPix projection to the rHEALPix projection.

EXAMPLES:

>>> rdggs = UNIT_003
>>> p = (0, 0)
>>> q = (-pi/4, pi/2)
>>> print(tuple(x.tolist() for x in rdggs.combine_triangles(*p)))
(0.0, 0.0)
>>> print(tuple(x.tolist() for x in my_round(rdggs.combine_triangles(*q), 14)))
(-2.35619449019234, 1.5707963267949)
grid(resolution)

Generator function for all the cells at resolution resolution.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> grid0 = rdggs.grid(0)
>>> print([str(x) for x in grid0])
['N', 'O', 'P', 'Q', 'R', 'S']
healpix(u, v, inverse=False)

Return the HEALPix projection of point (u, v) (or its inverse if inverse = True) appropriate to this rHEALPix DGGS.

EXAMPLES:

>>> rdggs = UNIT_003_RADIANS
>>> print(tuple(x.tolist() for x in my_round(rdggs.healpix(-pi, pi/2), 14)))
(-2.35619449019234, 1.5707963267949)

NOTE:

Uses pj_healpix instead of the PROJ.4 version of HEALPix.

interval(a, b)

Generator function for all the resolution max(a.resolution, b.resolution) cells between cell a and cell b (inclusive and with respect to the postorder ordering on cells). Note that a and b don’t have to lie at the same resolution.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> a = rdggs.cell(('N', 1))
>>> b = rdggs.cell(('N',))
>>> print([str(c) for c in list(rdggs.interval(a, b))])
['N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7', 'N8']
minimal_cover(resolution, points, plane=True)

Find the minimal set of resolution resolution cells that covers the list of points points. If plane = True, then assume points is a list of x-y coordinates in the planar DGGS. If plane = False, then assume points is a list of longitude-latitude coordinates in the ellipsoidal DGGS. This method will be made redundant by standard GIS rasterization tools that implement the rHEALPix projection.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> c1 = rdggs.cell(['N', 0, 2, 1])
>>> c2 = rdggs.cell(['P', 7, 3, 3])
>>> points = [c.nucleus() for c in [c1, c2]]
>>> for r in range(5):
...     cover = sorted(rdggs.minimal_cover(r, points))
...     print([str(c) for c in cover])
['N', 'P']
['N0', 'P7']
['N02', 'P73']
['N021', 'P733']
['N0214', 'P7334']
num_cells(res_1, res_2=None, subcells=False)

Return the number of cells of resolutions res_1 to res_2 (inclusive). Assume res_1 <= res_2. If subcells = True, then return the number of subcells at resolutions res_1 to res_2 (inclusive) of a cell at resolution res_1. If res_2=None and subcells=False, then return the number of cells at resolution `res_1. If res_2=None and subcells = True, then return the number of subcells from resolution res_1 to resolution self.max_resolution.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> rdggs.num_cells(0)
6
>>> rdggs.num_cells(0, 1)
60
>>> rdggs.num_cells(0, subcells=True)
231627523606480
>>> rdggs.num_cells(0, 1, subcells=True)
10
>>> rdggs.num_cells(5, 6, subcells=True)
10
random_cell(resolution=None)

Return a cell of the given resolution chosen uniformly at random from all cells at that resolution. If resolution=None, then the cell resolution is first chosen uniformly at random from [0,..,self.max_resolution].

EXAMPLES:

>>> print(RHEALPixDGGS().random_cell()) 
S480586367780080
random_point(plane=True)

Return a point in this DGGS sampled uniformly at random from the plane or from the ellipsoid.

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> print(E.random_point()) 
(-1.0999574573422948, 0.21029104897701129)
rhealpix(u, v, inverse=False, region='none')

Return the rHEALPix projection of the point (u, v) (or its inverse if inverse = True) appropriate to this rHEALPix DGGS.

EXAMPLES:

>>> rdggs = UNIT_003_RADIANS
>>> print(tuple(x.tolist() for x in my_round(rdggs.rhealpix(0, pi/3), 14)))
(-1.858272006684, 2.06871881030324)

NOTE:

Uses pj_rhealpix instead of the PROJ.4 version of rHEALPix.

triangle(x, y, inverse=True)

If inverse = False, then assume (x,y) lies in the image of the HEALPix projection that comes with this DGGS, and return the number of the HEALPix polar triangle (0, 1, 2, 3, or None) and the region (‘north_polar’, ‘south_polar’, or ‘equatorial’) that (x, y) lies in. If inverse = True, then assume (x, y) lies in the image of the rHEALPix projection that comes with this DGGS, map (x, y) to its HEALPix image (x’, y’), and return the number of the HEALPix polar triangle and the region that (x’, y’) lies in. If (x, y) lies in the equatorial region, then the triangle number returned is None.

OUTPUT:

The pair (triangle_number, region).

NOTES:

This is a wrapper for pjr.triangle().

EXAMPLES:

>>> rdggs = RHEALPixDGGS()
>>> c = rdggs.cell(['N', 7])
>>> print(rdggs.triangle(*c.nucleus(), inverse=True))
(0, 'north_polar')

>>> c = rdggs.cell(['N', 3])
>>> print(rdggs.triangle(*c.nucleus(), inverse=True))
(3, 'north_polar')

>>> c = rdggs.cell(['P', 3])
>>> print(rdggs.triangle(*c.nucleus(), inverse=True))
(None, 'equatorial')

>>> c = rdggs.cell(['S', 5, 2])
>>> print(rdggs.triangle(*c.nucleus(), inverse=True))
(1, 'south_polar')
xyz(u, v, lonlat=False)

Given a point (u, v) in the planar image of the rHEALPix projection, project it back to the ellipsoid and return its 3D rectangular coordinates. If lonlat = True, then assume (u, v) is a longitude-latitude point.

EXAMPLES:

>>> rdggs = UNIT_003_RADIANS
>>> print(tuple(x.tolist() for x in my_round(rdggs.xyz(0, pi/4, lonlat=True), 14)))
(0.70710678118655, 0.0, 0.70710678118655)
xyz_cube(u, v, lonlat=False)

Given a point (u, v) in the planar version of this rHEALPix DGGS, fold the rHEALPix image into a cube centered at the origin, and return the resulting point’s 3D rectangular coordinates. If lonlat = True, then assume (u, v) is a longitude-latitude point.

EXAMPLES:

>>> rdggs = UNIT_003
>>> print(tuple(x.tolist() for x in my_round(rdggs.xyz_cube(0, 0), 14)))
(0.78539816339745, 0.0, -0.78539816339745)
class rhealpixdggs.dggs.RhealPolygon(rdggs=<rhealpixdggs.dggs.RHEALPixDGGS object>, suid_list=None)

Bases: object