canvod.grids API reference¶
Hemispheric grid implementations and spatial analysis tools.
Package¶
HEALPix and hemispheric grid operations.
Provides hemisphere grid structures for GNSS signal observation analysis.
BaseGridBuilder
¶
Bases: ABC
Abstract base for hemispherical grid builders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees |
0
|
phi_rotation
|
float
|
Rotation angle in degrees (applied to all phi values) |
0
|
__init__(angular_resolution=2, cutoff_theta=0, phi_rotation=0)
¶
Initialize the grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
abstractmethod
¶
Get grid type identifier.
GridData
dataclass
¶
Immutable container for hemispherical grid structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
DataFrame
|
Grid cells with phi, theta, and bounds |
required |
theta_lims
|
ndarray
|
Theta band limits |
required |
phi_lims
|
list[ndarray]
|
Phi limits per theta band |
required |
cell_ids
|
list[ndarray]
|
Cell IDs per theta band |
required |
grid_type
|
str
|
Grid type identifier |
required |
solid_angles
|
ndarray
|
Solid angles per cell [steradians] |
None
|
metadata
|
dict
|
Additional grid metadata |
None
|
voronoi
|
Any
|
Voronoi tessellation object (for Fibonacci grids) |
None
|
vertices
|
ndarray
|
3D vertices (for triangular grids) |
None
|
points_xyz
|
ndarray
|
3D point cloud (for Fibonacci grids) |
None
|
vertex_phi
|
ndarray
|
Vertex phi coordinates |
None
|
vertex_theta
|
ndarray
|
Vertex theta coordinates |
None
|
GridType
¶
Bases: Enum
Available grid projection types for hemispherical tessellation.
EqualAreaBuilder
¶
Bases: BaseGridBuilder
Equal solid angle tessellation using concentric theta bands.
The hemisphere is divided into annular bands of constant width in theta. Within each band the number of azimuthal (phi) sectors is chosen so that every cell subtends approximately the same solid angle. This is the only grid type that has been validated for scientific use in this codebase.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle measured from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means
angular_resolution meansangular_resolution (degrees) sets the width of each theta band.
All bands have this same width Δθ. The azimuthal width of cells varies
by band: near the zenith cells are wide in phi; near the horizon they are
narrow, so that the solid angle stays constant.
Mathematical construction
-
Target solid angle per cell is chosen equal to the solid angle of a cap of half-angle Δθ/2::
Ω_target = 2π (1 − cos(Δθ/2))
-
Zenith cap – a single cell covers [0, Δθ/2] in theta and the full azimuth [0, 2π).
-
Theta bands – edges are placed at Δθ/2, 3Δθ/2, 5Δθ/2, … and a last edge at π/2 − cutoff_theta, so the grid reaches the horizon (or the cutoff). With the zenith cap of half width the last band is usually narrower than Δθ (half as wide when the cutoff is a multiple of Δθ); its phi divisions follow from its solid angle like any other band's. For each band [θ_inner, θ_outer] the band's total solid angle is::
Ω_band = 2π (cos θ_inner − cos θ_outer)
-
Phi divisions – the number of sectors in the band is::
n_phi = round(Ω_band / Ω_target)
Each sector spans Δφ = 2π / n_phi. The cell center is placed at the geometric midpoint of its (phi, theta) rectangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Theta-band width in degrees. Controls both the radial resolution and (indirectly, via the equal-area constraint) the azimuthal resolution. |
2
|
cutoff_theta
|
float
|
Minimum elevation above the horizon in degrees. The last band ends at θ = 90° − cutoff_theta; the sky below that elevation gets no cells. In GNSS terms this is the satellite elevation mask angle. |
0
|
phi_rotation
|
float
|
Rigid rotation applied to all phi coordinates after grid construction, in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
EqualAngleBuilder
¶
Bases: BaseGridBuilder
Equal angular spacing in both theta and phi (NOT equal area).
Every cell is a rectangle of the same angular size Δθ × Δφ in the
(theta, phi) parameter space. A cell's solid angle is
Δφ · (cos θ_min − cos θ_max) ≈ Δφ · Δθ · sin θ, so cells near the zenith
subtend less solid angle than cells near the horizon. A statistic
that weights every cell equally therefore gives the sky near the zenith
more weight than its share of the hemisphere. Not recommended for
scientific analysis – use EqualAreaBuilder instead.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith
What angular_resolution means
angular_resolution meansangular_resolution (degrees) is used as both the theta-band width
and the phi-sector width. The number of phi divisions is the same in
every band but the zenith cap::
n_phi = floor(2π / Δθ)
so the sectors are wider than Δθ when Δθ does not divide 360°.
Mathematical construction
- A zenith cap cell covers [0, Δθ/2] × [0, 2π).
- Theta band edges are placed at Δθ/2, 3Δθ/2, … and a last edge at π/2 − cutoff_theta, so the grid reaches the horizon (or the cutoff); the last band is usually narrower than Δθ.
- Within every band, the full azimuth is split into
n_phisectors of equal width Δφ = 2π / n_phi. - Cell centers are at the midpoint of each (phi, theta) rectangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular spacing in degrees, applied identically in both theta and phi. |
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. The last band ends at θ = 90° − cutoff_theta; the sky below that elevation gets no cells. |
0
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
EquirectangularBuilder
¶
Bases: BaseGridBuilder
Simple rectangular grid in (theta, phi) space.
The hemisphere is divided into a regular rectangular array: a constant
number of theta bands, each containing the same constant number of phi
sectors. Every cell is a rectangle of the same angular size in
(theta, phi), except the last band and the last sector, which are
narrower when Δθ does not divide their range. It differs from
EqualAngleBuilder at the zenith: EqualAngleBuilder makes the
innermost half band a single zenith cap and puts its band edges half a
band off, while this builder starts the first band at the zenith with
the same number of sectors as every other band.
A cell's solid angle is Δφ · (cos θ_min − cos θ_max) ≈ Δφ · Δθ · sin θ,
so cells near the zenith subtend less solid angle than cells near the
horizon. A statistic that weights every cell equally therefore gives
the sky near the zenith more weight than its share of the hemisphere.
Not recommended for scientific analysis – use EqualAreaBuilder
instead.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means
angular_resolution meansangular_resolution (degrees) is used as both the theta-band width
and the phi-sector width::
n_theta = ceil((π/2 − cutoff) / Δθ)
n_phi = ceil(2π / Δθ)
total cells = n_theta × n_phi
Mathematical construction
- Theta edges are placed at
0,Δθ,2Δθ, … up toπ/2 − cutoff_theta(the last edge is clamped exactly toπ/2 − cutoff_thetarather than overshooting it). - Phi edges are placed at 0, Δθ, 2Δθ, … and a last edge at 2π; the last sector is narrower when Δθ does not divide 360°.
- Every (theta_band, phi_sector) combination produces one cell. The cell center is the midpoint of the rectangle.
- No special zenith cap is created; the band nearest the zenith has the same number of phi sectors as all other bands.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular spacing in degrees, applied identically in both theta and phi. |
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. The last band ends at θ = 90° − cutoff_theta; the sky below that elevation gets no cells. |
0
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
HEALPixBuilder
¶
Bases: BaseGridBuilder
HEALPix tessellation (Hierarchical Equal Area isoLatitude Pixelization).
HEALPix partitions the sphere into 12 base pixels arranged at equal
latitudes. Each base pixel is recursively subdivided into 4 children,
producing 12 × nside² pixels on the full sphere, all with exactly
the same solid angle. This strict equal-area property makes HEALPix
the gold standard for pixelisations that must be unbiased under
solid-angle weighting.
This builder delegates the pixel geometry entirely to the healpy
library. It filters the full-sphere pixelisation down to the northern
hemisphere and stores approximate bounding boxes (phi_min/max,
theta_min/max) derived from the pixel resolution. The bounding
boxes are not the true pixel boundaries (which are curvilinear);
they are only approximations suitable for quick spatial queries. For
exact pixel membership use healpy.ang2pix directly.
Coordinate convention
HEALPix natively uses colatitude theta ∈ [0, π] (0 = North Pole)
and longitude phi ∈ [0, 2π). This matches the GNSS convention used
elsewhere in canvodpy: theta = 0 is the zenith, theta = π/2 is the
horizon. No coordinate transform is applied.
What nside (resolution) means
nside (resolution) meansnside is the single resolution parameter of HEALPix. It must be a
power of 2. The key derived quantities are::
n_pixels = 12 × nside² (full sphere)
pixel_area = 4π / n_pixels (steradians, exact)
resolution ≈ √(pixel_area) (approximate angular diameter)
≈ 58.6° / nside (degrees)
| nside | Pixels (full) | Approx resolution | Pixel area (sr) |
|---|---|---|---|
| 1 | 12 | 58.6° | 1.049 |
| 2 | 48 | 29.3° | 0.262 |
| 4 | 192 | 14.7° | 0.065 |
| 8 | 768 | 7.3° | 0.016 |
| 16 | 3 072 | 3.7° | 0.004 |
| 32 | 12 288 | 1.8° | 0.001 |
When nside is not provided, it is estimated from angular_resolution
and rounded to the nearest power of 2::
nside_estimate = round_to_pow2( √(3/π) × 60 / angular_resolution )
Mathematical construction
HEALPix construction is performed entirely by healpy. At a high
level:
- The sphere is divided into 12 congruent base pixels (a curvilinear quadrilateral arrangement at three latitude zones: polar caps and equatorial belt).
- Each base pixel is subdivided into
nside²equal-area children using a hierarchical quadtree. - Pixel centers are returned by
healpy.pix2ang(nside, ipix)in RING ordering (pixels ordered by increasing colatitude). - This builder keeps only pixels with
theta ≤ π/2 − cutoff_theta(northern hemisphere above the elevation mask).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to derive
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Pixels with colatitude
|
0
|
nside
|
int or None
|
HEALPix resolution parameter. Must be a power of 2. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
ValueError
|
If |
__init__(angular_resolution=2, cutoff_theta=0, nside=None, phi_rotation=0)
¶
Initialize the HEALPix grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
nside
|
int | None
|
HEALPix nside parameter. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
get_healpix_info()
¶
Get HEALPix-specific information.
Returns:
| Name | Type | Description |
|---|---|---|
info |
dict
|
Keys: |
GeodesicBuilder
¶
Bases: BaseGridBuilder
Geodesic grid based on a subdivided icosahedron.
The sphere is tessellated into triangular cells by starting with an icosahedron (20 equilateral triangles) and recursively subdividing each triangle into four smaller triangles. All vertices are projected back onto the unit sphere after each subdivision step, so the final cells are spherical triangles. The grid has no polar singularity and provides near-uniform cell areas, though strict equal-area is not guaranteed — cell areas vary by a few percent depending on how they inherit the icosahedral symmetry axes.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centers are computed as the 3D Cartesian mean of the three vertices, re-normalized onto the unit sphere.
What angular_resolution means
angular_resolution meansangular_resolution is not used directly as a cell size. Instead it
is used only when subdivision_level is not explicitly supplied, to
estimate an appropriate subdivision level. The heuristic targets an
approximate triangle edge length of 2 × angular_resolution::
target_edge ≈ 2 × angular_resolution (degrees)
subdivision_level = ceil(log₂(63.4 / target_edge))
The number 63.4° is the edge length of a regular icosahedron inscribed in a unit sphere. Each subdivision halves the edge length, so the actual edge length at level n is approximately::
edge ≈ 63.4° / 2ⁿ (degrees)
The total number of triangles on the full sphere is 20 × 4ⁿ.
Roughly half fall in the northern hemisphere (exact count depends on
the hemisphere boundary).
Mathematical construction
- Icosahedron – 12 vertices placed at the intersections of three mutually perpendicular golden-ratio rectangles, normalized to the unit sphere. 20 triangular faces connect them.
- Subdivision – each triangle is split into 4 by inserting edge
midpoints. Each midpoint is projected onto the unit sphere
(re-normalized) before the next subdivision. This is repeated
subdivision_leveltimes. - Hemisphere filter – faces are kept if their centroid (the
3D Cartesian mean of the three vertices, renormalised to the unit
sphere) satisfies
theta ≤ π/2 − cutoff_theta. Filtering on centroid rather than "any vertex above" avoids keeping boundary triangles that are mostly below the horizon; kept triangles may still extend slightly across the horizon since cells are not clipped to an exact circle. - Phi wrapping – for triangles that straddle the 0/2π azimuthal
boundary, bounding-box limits are computed with a seam-aware
min/max (see
phi_bbox) rather than naivemin/max.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to derive
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Triangles are excluded if their centroid is below this elevation. |
0
|
subdivision_level
|
int or None
|
Number of icosahedral subdivisions. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Notes
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout. Use the geodesic_vertices column and the
vertices array in GridData.vertices for the true geometry.
__init__(angular_resolution=2, cutoff_theta=0, subdivision_level=None, phi_rotation=0)
¶
Initialize the geodesic grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
subdivision_level
|
int | None
|
Subdivision level override. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_triangles()
¶
Return triangle vertex coordinates for visualization.
Returns:
| Name | Type | Description |
|---|---|---|
triangles |
ndarray or None
|
Array of shape |
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
FibonacciBuilder
¶
Bases: BaseGridBuilder
Fibonacci sphere grid with spherical Voronoi tessellation.
Points are distributed on the sphere using the Fibonacci lattice (golden-spiral method), which provides one of the most uniform point distributions achievable on a sphere without iterative optimization. Each point then becomes the center of a spherical Voronoi cell — the region of the sphere closer to that point than to any other. The resulting tessellation has no polar singularities and near-uniform cell areas.
The tessellation is computed by scipy.spatial.SphericalVoronoi.
Because Voronoi cells have curvilinear boundaries, the phi_min/max
and theta_min/max columns in the grid are axis-aligned bounding
boxes, not the true cell boundaries. They are unreliable for
spatial queries — use the voronoi_region column (vertex indices
into the SphericalVoronoi.vertices array) for exact geometry.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What n_points (resolution) means
n_points (resolution) meansResolution is controlled by n_points, the number of Voronoi cells
in the hemisphere. When n_points is not supplied it is estimated
from angular_resolution via::
cell_area ≈ angular_resolution² (radians²)
n_points = max(10, round(2π / cell_area))
The approximate cell "diameter" (assuming a circular cell of equal area) is::
d ≈ 2 √(2π / n_points) (radians)
≈ 2 × angular_resolution
angular_resolution therefore has no direct geometric meaning for
this grid type — it is only a convenience for the n_points estimator.
Mathematical construction
-
Full-sphere Fibonacci lattice –
2 × n_pointspoints are generated on the unit sphere. Point i has::θᵢ = arccos(1 − 2(i + 0.5) / N) φᵢ = 2π (i + 0.5) / φ_golden (mod 2π)
where N = 2 × n_points and φ_golden = (1+√5)/2. The
+0.5 offset avoids placing points exactly at the poles.
2. Spherical Voronoi tessellation –
scipy.spatial.SphericalVoronoi computes the Voronoi diagram on
the full sphere (both hemispheres). Regions are sorted so that
vertices appear in counter-clockwise order around each cell.
Tessellating the full sphere (rather than hemisphere-only points)
is required for correctness: with no sites in the southern
hemisphere, cells along the horizon would have no southern
neighbor to bound them and would balloon out to cover the entire
(empty) opposite hemisphere.
3. Hemisphere filter – of the resulting cells, only those whose
generating point has θ ≤ π/2 − cutoff_theta are kept.
4. Bounding boxes – axis-aligned bounding boxes in (phi, theta)
are computed from the Voronoi vertex coordinates, using a
seam-aware min/max so cells straddling phi = 0/2π get their
true (narrow) angular extent instead of a near-full-circle span.
These bounding boxes are still approximations only (see caveat
above).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to estimate
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Points below this elevation are excluded before tessellation. |
0
|
n_points
|
int or None
|
Target number of Voronoi cells in the hemisphere. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
ValueError
|
If fewer than 4 points survive the hemisphere filter. |
Notes
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
Voronoi cell layout.
__init__(angular_resolution=2, cutoff_theta=0, n_points=None, phi_rotation=0)
¶
Initialize the Fibonacci grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
n_points
|
int | None
|
Number of points to generate. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
HTMBuilder
¶
Bases: BaseGridBuilder
Hierarchical Triangular Mesh (HTM) grid.
HTM divides the sphere into an octahedron (8 triangular faces), then
recursively subdivides each face into 4 smaller triangles by inserting
edge-midpoint vertices projected onto the unit sphere. The recursion
depth is controlled by htm_level. This produces a strictly
hierarchical triangulation: every triangle at level n is the union of
exactly 4 triangles at level n + 1.
Cell areas are approximately equal but not strictly so — area uniformity improves with level because the icosahedral edge-length asymmetry averages out over many subdivisions.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centers are the 3D Cartesian mean of the three triangle vertices, re-normalized onto the unit sphere.
What htm_level (resolution) means
htm_level (resolution) meansThe resolution is set by htm_level, not by angular_resolution.
angular_resolution is used only to estimate an appropriate level
when htm_level is not supplied explicitly. The heuristic is::
target_edge ≈ 2 × angular_resolution (degrees)
htm_level = min(15, ceil(log₂(90 / target_edge)))
The approximate triangle edge length at level n is::
edge ≈ 90° / 2ⁿ
| Level | Triangles (full sphere) | Approx edge |
|---|---|---|
| 0 | 8 | 90° |
| 1 | 32 | 45° |
| 2 | 128 | 22.5° |
| 3 | 512 | 11.25° |
| 4 | 2 048 | 5.6° |
| n | 8 × 4ⁿ | 90° / 2ⁿ |
Mathematical construction
- Octahedron – 6 vertices at ±x, ±y, ±z on the unit sphere, forming 8 triangular faces (4 northern, 4 southern).
-
Subdivision – for each triangle [v₀, v₁, v₂], three edge midpoints are computed and projected onto the unit sphere::
m₀ = normalize((v₀ + v₁) / 2) m₁ = normalize((v₁ + v₂) / 2) m₂ = normalize((v₂ + v₀) / 2)
The four children are [v₀, m₀, m₂], [v₁, m₁, m₀], [v₂, m₂, m₁],
and [m₀, m₁, m₂]. This is repeated htm_level times.
3. Hemisphere filter – a triangle is kept if its centroid (the
3D Cartesian mean of the three vertices, renormalised to the unit
sphere) satisfies theta ≤ π/2 − cutoff_theta. Filtering on
centroid rather than "any vertex above" avoids keeping boundary
triangles that are mostly below the horizon; kept triangles may
still extend slightly across the horizon since cells are not
clipped to an exact circle.
4. Each leaf triangle becomes one cell; its center, bounding box, and
three vertex coordinates are stored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to derive
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Triangles are excluded if their centroid is below this elevation. |
0
|
htm_level
|
int or None
|
HTM subdivision depth. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Notes
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout.
HTM IDs in this implementation use a decimal-digit scheme
(parent_id × 10 + child_index) which diverges from the original
SDSS HTM binary-coded ID scheme. This is adequate for indexing but
should not be compared with external HTM catalogs.
References
Kunszt et al. (2001): "The Hierarchical Triangular Mesh" https://www.sdss.org/dr12/algorithms/htm/
__init__(angular_resolution=2, cutoff_theta=0, htm_level=None, phi_rotation=0)
¶
Initialize the HTM grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
htm_level
|
int | None
|
HTM subdivision level. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
get_htm_info()
¶
Get HTM-specific information.
Returns:
| Name | Type | Description |
|---|---|---|
info |
dict
|
Keys: |
CellAggregator
¶
Polars-based per-cell aggregation helpers.
aggregate_by_cell(df, value_var='VOD', method='mean')
staticmethod
¶
Aggregate values by cell_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Must contain |
required |
value_var
|
str
|
Column to aggregate. |
'VOD'
|
method
|
('mean', 'median', 'std', 'count')
|
Aggregation method. |
'mean'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Two-column DataFrame: |
create_hemigrid(grid_type, angular_resolution=10.0, **kwargs)
¶
Create hemisphere grid of specified type.
Factory function for creating various hemisphere grid types commonly used in GNSS analysis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid_type
|
str
|
Type of grid to create: - 'equal_area': Regular lat/lon grid with equal solid angle cells - 'equal_angle': Regular angular spacing (not recommended) - 'rectangular' or 'equirectangular': Simple rectangular grid - 'HTM': Hierarchical Triangular Mesh - 'geodesic': Geodesic sphere subdivision (icosahedron-based) - 'healpix': HEALPix grid (requires healpy) - 'fibonacci': Fibonacci sphere (requires scipy) |
required |
angular_resolution
|
float
|
Angular resolution in degrees |
10.0
|
**kwargs
|
Any
|
Additional grid-specific parameters: - cutoff_theta : float - Maximum theta angle cutoff (degrees) - phi_rotation : float - Rotation angle (degrees) - subdivision_level : int - For geodesic/HTM grids - htm_level : int - For HTM grids specifically - nside : int - For HEALPix grids - n_points : int - For Fibonacci grids |
{}
|
Returns:
| Type | Description |
|---|---|
GridData
|
Complete hemisphere grid data structure |
Examples:
>>> # Equal area grid with 10° resolution
>>> grid = create_hemigrid('equal_area', angular_resolution=10.0)
>>>
>>> # HTM grid with subdivision level 3
>>> grid = create_hemigrid('HTM', angular_resolution=5.0, htm_level=3)
>>>
>>> # Geodesic grid
>>> grid = create_hemigrid('geodesic', angular_resolution=5.0, subdivision_level=2)
Notes
Grid coordinates use navigation convention: - phi: azimuth angle, 0 to 2π (0 = North, π/2 = East, clockwise) - theta: polar angle from zenith, 0 to π/2 (0 = zenith, π/2 = horizon)
Grid core¶
Base class for hemisphere grid builders.
BaseGridBuilder
¶
Bases: ABC
Abstract base for hemispherical grid builders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees |
0
|
phi_rotation
|
float
|
Rotation angle in degrees (applied to all phi values) |
0
|
__init__(angular_resolution=2, cutoff_theta=0, phi_rotation=0)
¶
Initialize the grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
abstractmethod
¶
Get grid type identifier.
Grid data container for hemisphere grids.
GridData
dataclass
¶
Immutable container for hemispherical grid structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
DataFrame
|
Grid cells with phi, theta, and bounds |
required |
theta_lims
|
ndarray
|
Theta band limits |
required |
phi_lims
|
list[ndarray]
|
Phi limits per theta band |
required |
cell_ids
|
list[ndarray]
|
Cell IDs per theta band |
required |
grid_type
|
str
|
Grid type identifier |
required |
solid_angles
|
ndarray
|
Solid angles per cell [steradians] |
None
|
metadata
|
dict
|
Additional grid metadata |
None
|
voronoi
|
Any
|
Voronoi tessellation object (for Fibonacci grids) |
None
|
vertices
|
ndarray
|
3D vertices (for triangular grids) |
None
|
points_xyz
|
ndarray
|
3D point cloud (for Fibonacci grids) |
None
|
vertex_phi
|
ndarray
|
Vertex phi coordinates |
None
|
vertex_theta
|
ndarray
|
Vertex theta coordinates |
None
|
Grid type definitions for hemisphere tessellation.
GridType
¶
Bases: Enum
Available grid projection types for hemispherical tessellation.
Grid builders¶
Equal-area grid¶
Equal-area grid implementation.
EqualAreaBuilder
¶
Bases: BaseGridBuilder
Equal solid angle tessellation using concentric theta bands.
The hemisphere is divided into annular bands of constant width in theta. Within each band the number of azimuthal (phi) sectors is chosen so that every cell subtends approximately the same solid angle. This is the only grid type that has been validated for scientific use in this codebase.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle measured from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means
angular_resolution meansangular_resolution (degrees) sets the width of each theta band.
All bands have this same width Δθ. The azimuthal width of cells varies
by band: near the zenith cells are wide in phi; near the horizon they are
narrow, so that the solid angle stays constant.
Mathematical construction
-
Target solid angle per cell is chosen equal to the solid angle of a cap of half-angle Δθ/2::
Ω_target = 2π (1 − cos(Δθ/2))
-
Zenith cap – a single cell covers [0, Δθ/2] in theta and the full azimuth [0, 2π).
-
Theta bands – edges are placed at Δθ/2, 3Δθ/2, 5Δθ/2, … and a last edge at π/2 − cutoff_theta, so the grid reaches the horizon (or the cutoff). With the zenith cap of half width the last band is usually narrower than Δθ (half as wide when the cutoff is a multiple of Δθ); its phi divisions follow from its solid angle like any other band's. For each band [θ_inner, θ_outer] the band's total solid angle is::
Ω_band = 2π (cos θ_inner − cos θ_outer)
-
Phi divisions – the number of sectors in the band is::
n_phi = round(Ω_band / Ω_target)
Each sector spans Δφ = 2π / n_phi. The cell center is placed at the geometric midpoint of its (phi, theta) rectangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Theta-band width in degrees. Controls both the radial resolution and (indirectly, via the equal-area constraint) the azimuthal resolution. |
2
|
cutoff_theta
|
float
|
Minimum elevation above the horizon in degrees. The last band ends at θ = 90° − cutoff_theta; the sky below that elevation gets no cells. In GNSS terms this is the satellite elevation mask angle. |
0
|
phi_rotation
|
float
|
Rigid rotation applied to all phi coordinates after grid construction, in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
Equal-angle grid¶
Equal-angle grid implementation.
EqualAngleBuilder
¶
Bases: BaseGridBuilder
Equal angular spacing in both theta and phi (NOT equal area).
Every cell is a rectangle of the same angular size Δθ × Δφ in the
(theta, phi) parameter space. A cell's solid angle is
Δφ · (cos θ_min − cos θ_max) ≈ Δφ · Δθ · sin θ, so cells near the zenith
subtend less solid angle than cells near the horizon. A statistic
that weights every cell equally therefore gives the sky near the zenith
more weight than its share of the hemisphere. Not recommended for
scientific analysis – use EqualAreaBuilder instead.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith
What angular_resolution means
angular_resolution meansangular_resolution (degrees) is used as both the theta-band width
and the phi-sector width. The number of phi divisions is the same in
every band but the zenith cap::
n_phi = floor(2π / Δθ)
so the sectors are wider than Δθ when Δθ does not divide 360°.
Mathematical construction
- A zenith cap cell covers [0, Δθ/2] × [0, 2π).
- Theta band edges are placed at Δθ/2, 3Δθ/2, … and a last edge at π/2 − cutoff_theta, so the grid reaches the horizon (or the cutoff); the last band is usually narrower than Δθ.
- Within every band, the full azimuth is split into
n_phisectors of equal width Δφ = 2π / n_phi. - Cell centers are at the midpoint of each (phi, theta) rectangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular spacing in degrees, applied identically in both theta and phi. |
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. The last band ends at θ = 90° − cutoff_theta; the sky below that elevation gets no cells. |
0
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
Equirectangular grid¶
Equirectangular grid implementation.
EquirectangularBuilder
¶
Bases: BaseGridBuilder
Simple rectangular grid in (theta, phi) space.
The hemisphere is divided into a regular rectangular array: a constant
number of theta bands, each containing the same constant number of phi
sectors. Every cell is a rectangle of the same angular size in
(theta, phi), except the last band and the last sector, which are
narrower when Δθ does not divide their range. It differs from
EqualAngleBuilder at the zenith: EqualAngleBuilder makes the
innermost half band a single zenith cap and puts its band edges half a
band off, while this builder starts the first band at the zenith with
the same number of sectors as every other band.
A cell's solid angle is Δφ · (cos θ_min − cos θ_max) ≈ Δφ · Δθ · sin θ,
so cells near the zenith subtend less solid angle than cells near the
horizon. A statistic that weights every cell equally therefore gives
the sky near the zenith more weight than its share of the hemisphere.
Not recommended for scientific analysis – use EqualAreaBuilder
instead.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means
angular_resolution meansangular_resolution (degrees) is used as both the theta-band width
and the phi-sector width::
n_theta = ceil((π/2 − cutoff) / Δθ)
n_phi = ceil(2π / Δθ)
total cells = n_theta × n_phi
Mathematical construction
- Theta edges are placed at
0,Δθ,2Δθ, … up toπ/2 − cutoff_theta(the last edge is clamped exactly toπ/2 − cutoff_thetarather than overshooting it). - Phi edges are placed at 0, Δθ, 2Δθ, … and a last edge at 2π; the last sector is narrower when Δθ does not divide 360°.
- Every (theta_band, phi_sector) combination produces one cell. The cell center is the midpoint of the rectangle.
- No special zenith cap is created; the band nearest the zenith has the same number of phi sectors as all other bands.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular spacing in degrees, applied identically in both theta and phi. |
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. The last band ends at θ = 90° − cutoff_theta; the sky below that elevation gets no cells. |
0
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
HEALPix grid¶
HEALPix grid implementation.
HEALPixBuilder
¶
Bases: BaseGridBuilder
HEALPix tessellation (Hierarchical Equal Area isoLatitude Pixelization).
HEALPix partitions the sphere into 12 base pixels arranged at equal
latitudes. Each base pixel is recursively subdivided into 4 children,
producing 12 × nside² pixels on the full sphere, all with exactly
the same solid angle. This strict equal-area property makes HEALPix
the gold standard for pixelisations that must be unbiased under
solid-angle weighting.
This builder delegates the pixel geometry entirely to the healpy
library. It filters the full-sphere pixelisation down to the northern
hemisphere and stores approximate bounding boxes (phi_min/max,
theta_min/max) derived from the pixel resolution. The bounding
boxes are not the true pixel boundaries (which are curvilinear);
they are only approximations suitable for quick spatial queries. For
exact pixel membership use healpy.ang2pix directly.
Coordinate convention
HEALPix natively uses colatitude theta ∈ [0, π] (0 = North Pole)
and longitude phi ∈ [0, 2π). This matches the GNSS convention used
elsewhere in canvodpy: theta = 0 is the zenith, theta = π/2 is the
horizon. No coordinate transform is applied.
What nside (resolution) means
nside (resolution) meansnside is the single resolution parameter of HEALPix. It must be a
power of 2. The key derived quantities are::
n_pixels = 12 × nside² (full sphere)
pixel_area = 4π / n_pixels (steradians, exact)
resolution ≈ √(pixel_area) (approximate angular diameter)
≈ 58.6° / nside (degrees)
| nside | Pixels (full) | Approx resolution | Pixel area (sr) |
|---|---|---|---|
| 1 | 12 | 58.6° | 1.049 |
| 2 | 48 | 29.3° | 0.262 |
| 4 | 192 | 14.7° | 0.065 |
| 8 | 768 | 7.3° | 0.016 |
| 16 | 3 072 | 3.7° | 0.004 |
| 32 | 12 288 | 1.8° | 0.001 |
When nside is not provided, it is estimated from angular_resolution
and rounded to the nearest power of 2::
nside_estimate = round_to_pow2( √(3/π) × 60 / angular_resolution )
Mathematical construction
HEALPix construction is performed entirely by healpy. At a high
level:
- The sphere is divided into 12 congruent base pixels (a curvilinear quadrilateral arrangement at three latitude zones: polar caps and equatorial belt).
- Each base pixel is subdivided into
nside²equal-area children using a hierarchical quadtree. - Pixel centers are returned by
healpy.pix2ang(nside, ipix)in RING ordering (pixels ordered by increasing colatitude). - This builder keeps only pixels with
theta ≤ π/2 − cutoff_theta(northern hemisphere above the elevation mask).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to derive
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Pixels with colatitude
|
0
|
nside
|
int or None
|
HEALPix resolution parameter. Must be a power of 2. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
ValueError
|
If |
__init__(angular_resolution=2, cutoff_theta=0, nside=None, phi_rotation=0)
¶
Initialize the HEALPix grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
nside
|
int | None
|
HEALPix nside parameter. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
get_healpix_info()
¶
Get HEALPix-specific information.
Returns:
| Name | Type | Description |
|---|---|---|
info |
dict
|
Keys: |
Geodesic grid¶
Geodesic grid implementation.
GeodesicBuilder
¶
Bases: BaseGridBuilder
Geodesic grid based on a subdivided icosahedron.
The sphere is tessellated into triangular cells by starting with an icosahedron (20 equilateral triangles) and recursively subdividing each triangle into four smaller triangles. All vertices are projected back onto the unit sphere after each subdivision step, so the final cells are spherical triangles. The grid has no polar singularity and provides near-uniform cell areas, though strict equal-area is not guaranteed — cell areas vary by a few percent depending on how they inherit the icosahedral symmetry axes.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centers are computed as the 3D Cartesian mean of the three vertices, re-normalized onto the unit sphere.
What angular_resolution means
angular_resolution meansangular_resolution is not used directly as a cell size. Instead it
is used only when subdivision_level is not explicitly supplied, to
estimate an appropriate subdivision level. The heuristic targets an
approximate triangle edge length of 2 × angular_resolution::
target_edge ≈ 2 × angular_resolution (degrees)
subdivision_level = ceil(log₂(63.4 / target_edge))
The number 63.4° is the edge length of a regular icosahedron inscribed in a unit sphere. Each subdivision halves the edge length, so the actual edge length at level n is approximately::
edge ≈ 63.4° / 2ⁿ (degrees)
The total number of triangles on the full sphere is 20 × 4ⁿ.
Roughly half fall in the northern hemisphere (exact count depends on
the hemisphere boundary).
Mathematical construction
- Icosahedron – 12 vertices placed at the intersections of three mutually perpendicular golden-ratio rectangles, normalized to the unit sphere. 20 triangular faces connect them.
- Subdivision – each triangle is split into 4 by inserting edge
midpoints. Each midpoint is projected onto the unit sphere
(re-normalized) before the next subdivision. This is repeated
subdivision_leveltimes. - Hemisphere filter – faces are kept if their centroid (the
3D Cartesian mean of the three vertices, renormalised to the unit
sphere) satisfies
theta ≤ π/2 − cutoff_theta. Filtering on centroid rather than "any vertex above" avoids keeping boundary triangles that are mostly below the horizon; kept triangles may still extend slightly across the horizon since cells are not clipped to an exact circle. - Phi wrapping – for triangles that straddle the 0/2π azimuthal
boundary, bounding-box limits are computed with a seam-aware
min/max (see
phi_bbox) rather than naivemin/max.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to derive
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Triangles are excluded if their centroid is below this elevation. |
0
|
subdivision_level
|
int or None
|
Number of icosahedral subdivisions. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Notes
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout. Use the geodesic_vertices column and the
vertices array in GridData.vertices for the true geometry.
__init__(angular_resolution=2, cutoff_theta=0, subdivision_level=None, phi_rotation=0)
¶
Initialize the geodesic grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
subdivision_level
|
int | None
|
Subdivision level override. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_triangles()
¶
Return triangle vertex coordinates for visualization.
Returns:
| Name | Type | Description |
|---|---|---|
triangles |
ndarray or None
|
Array of shape |
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
Fibonacci grid¶
Fibonacci sphere grid implementation.
FibonacciBuilder
¶
Bases: BaseGridBuilder
Fibonacci sphere grid with spherical Voronoi tessellation.
Points are distributed on the sphere using the Fibonacci lattice (golden-spiral method), which provides one of the most uniform point distributions achievable on a sphere without iterative optimization. Each point then becomes the center of a spherical Voronoi cell — the region of the sphere closer to that point than to any other. The resulting tessellation has no polar singularities and near-uniform cell areas.
The tessellation is computed by scipy.spatial.SphericalVoronoi.
Because Voronoi cells have curvilinear boundaries, the phi_min/max
and theta_min/max columns in the grid are axis-aligned bounding
boxes, not the true cell boundaries. They are unreliable for
spatial queries — use the voronoi_region column (vertex indices
into the SphericalVoronoi.vertices array) for exact geometry.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What n_points (resolution) means
n_points (resolution) meansResolution is controlled by n_points, the number of Voronoi cells
in the hemisphere. When n_points is not supplied it is estimated
from angular_resolution via::
cell_area ≈ angular_resolution² (radians²)
n_points = max(10, round(2π / cell_area))
The approximate cell "diameter" (assuming a circular cell of equal area) is::
d ≈ 2 √(2π / n_points) (radians)
≈ 2 × angular_resolution
angular_resolution therefore has no direct geometric meaning for
this grid type — it is only a convenience for the n_points estimator.
Mathematical construction
-
Full-sphere Fibonacci lattice –
2 × n_pointspoints are generated on the unit sphere. Point i has::θᵢ = arccos(1 − 2(i + 0.5) / N) φᵢ = 2π (i + 0.5) / φ_golden (mod 2π)
where N = 2 × n_points and φ_golden = (1+√5)/2. The
+0.5 offset avoids placing points exactly at the poles.
2. Spherical Voronoi tessellation –
scipy.spatial.SphericalVoronoi computes the Voronoi diagram on
the full sphere (both hemispheres). Regions are sorted so that
vertices appear in counter-clockwise order around each cell.
Tessellating the full sphere (rather than hemisphere-only points)
is required for correctness: with no sites in the southern
hemisphere, cells along the horizon would have no southern
neighbor to bound them and would balloon out to cover the entire
(empty) opposite hemisphere.
3. Hemisphere filter – of the resulting cells, only those whose
generating point has θ ≤ π/2 − cutoff_theta are kept.
4. Bounding boxes – axis-aligned bounding boxes in (phi, theta)
are computed from the Voronoi vertex coordinates, using a
seam-aware min/max so cells straddling phi = 0/2π get their
true (narrow) angular extent instead of a near-full-circle span.
These bounding boxes are still approximations only (see caveat
above).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to estimate
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Points below this elevation are excluded before tessellation. |
0
|
n_points
|
int or None
|
Target number of Voronoi cells in the hemisphere. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
ValueError
|
If fewer than 4 points survive the hemisphere filter. |
Notes
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
Voronoi cell layout.
__init__(angular_resolution=2, cutoff_theta=0, n_points=None, phi_rotation=0)
¶
Initialize the Fibonacci grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
n_points
|
int | None
|
Number of points to generate. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
HTM grid¶
HTM (Hierarchical Triangular Mesh) grid implementation.
HTMBuilder
¶
Bases: BaseGridBuilder
Hierarchical Triangular Mesh (HTM) grid.
HTM divides the sphere into an octahedron (8 triangular faces), then
recursively subdivides each face into 4 smaller triangles by inserting
edge-midpoint vertices projected onto the unit sphere. The recursion
depth is controlled by htm_level. This produces a strictly
hierarchical triangulation: every triangle at level n is the union of
exactly 4 triangles at level n + 1.
Cell areas are approximately equal but not strictly so — area uniformity improves with level because the icosahedral edge-length asymmetry averages out over many subdivisions.
Coordinate convention (physics / GNSS)
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centers are the 3D Cartesian mean of the three triangle vertices, re-normalized onto the unit sphere.
What htm_level (resolution) means
htm_level (resolution) meansThe resolution is set by htm_level, not by angular_resolution.
angular_resolution is used only to estimate an appropriate level
when htm_level is not supplied explicitly. The heuristic is::
target_edge ≈ 2 × angular_resolution (degrees)
htm_level = min(15, ceil(log₂(90 / target_edge)))
The approximate triangle edge length at level n is::
edge ≈ 90° / 2ⁿ
| Level | Triangles (full sphere) | Approx edge |
|---|---|---|
| 0 | 8 | 90° |
| 1 | 32 | 45° |
| 2 | 128 | 22.5° |
| 3 | 512 | 11.25° |
| 4 | 2 048 | 5.6° |
| n | 8 × 4ⁿ | 90° / 2ⁿ |
Mathematical construction
- Octahedron – 6 vertices at ±x, ±y, ±z on the unit sphere, forming 8 triangular faces (4 northern, 4 southern).
-
Subdivision – for each triangle [v₀, v₁, v₂], three edge midpoints are computed and projected onto the unit sphere::
m₀ = normalize((v₀ + v₁) / 2) m₁ = normalize((v₁ + v₂) / 2) m₂ = normalize((v₂ + v₀) / 2)
The four children are [v₀, m₀, m₂], [v₁, m₁, m₀], [v₂, m₂, m₁],
and [m₀, m₁, m₂]. This is repeated htm_level times.
3. Hemisphere filter – a triangle is kept if its centroid (the
3D Cartesian mean of the three vertices, renormalised to the unit
sphere) satisfies theta ≤ π/2 − cutoff_theta. Filtering on
centroid rather than "any vertex above" avoids keeping boundary
triangles that are mostly below the horizon; kept triangles may
still extend slightly across the horizon since cells are not
clipped to an exact circle.
4. Each leaf triangle becomes one cell; its center, bounding box, and
three vertex coordinates are stored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Approximate angular resolution in degrees. Used only to derive
|
2
|
cutoff_theta
|
float
|
Elevation mask angle in degrees. Triangles are excluded if their centroid is below this elevation. |
0
|
htm_level
|
int or None
|
HTM subdivision depth. If |
None
|
phi_rotation
|
float
|
Rigid azimuthal rotation applied after construction, in degrees. |
0
|
Notes
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout.
HTM IDs in this implementation use a decimal-digit scheme
(parent_id × 10 + child_index) which diverges from the original
SDSS HTM binary-coded ID scheme. This is adequate for indexing but
should not be compared with external HTM catalogs.
References
Kunszt et al. (2001): "The Hierarchical Triangular Mesh" https://www.sdss.org/dr12/algorithms/htm/
__init__(angular_resolution=2, cutoff_theta=0, htm_level=None, phi_rotation=0)
¶
Initialize the HTM grid builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angular_resolution
|
float
|
Angular resolution in degrees. |
2
|
cutoff_theta
|
float
|
Maximum polar angle cutoff in degrees. |
0
|
htm_level
|
int | None
|
HTM subdivision level. |
None
|
phi_rotation
|
float
|
Rotation angle in degrees. |
0
|
get_grid_type()
¶
Return the grid-type identifier string.
Returns:
| Type | Description |
|---|---|
str
|
|
get_htm_info()
¶
Get HTM-specific information.
Returns:
| Name | Type | Description |
|---|---|---|
info |
dict
|
Keys: |
Grid operations¶
Cell assignment and vertex extraction for hemisphere grids.
Functions in this module operate on :class:~canvod.grids.core.GridData
instances and VOD xarray Datasets.
Cell assignment
add_cell_ids_to_ds_fast – the cell that contains each observation, eager or dask-lazy
Vertex / grid conversion
extract_grid_vertices – flat (x, y, z) arrays for 3-D visualization
grid_to_dataset – xarray Dataset with vertices and solid angles
add_cell_ids_to_ds_fast(ds, grid, grid_name)
¶
Assign every observation to the grid cell that contains it.
Each grid type is looked up by its own cell boundaries: theta band and
phi sector for equal_area, equal_angle and equirectangular;
the spherical triangle for htm and geodesic; the pixel for
healpix; the Voronoi cell for fibonacci. Bands and sectors are
closed at their inner edge and open at their outer edge; the last band
also includes its outer edge.
An observation outside the grid gets NaN: below the outer edge of the
last band of a ring grid (the horizon, or 90° - cutoff_theta), or,
for the triangle, pixel and Voronoi grids, in a cell the hemisphere
filter left out (these grids keep a cell when its center lies above
90° - cutoff_theta, so their edge near the horizon is not a circle).
phi and theta may be data variables (as the runs write them) or
coordinates, with (epoch, sid) dimensions in either order. If they
are dask arrays, the cell IDs are computed lazily, block by block.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Dataset with |
required |
grid
|
GridData
|
Hemisphere grid built with |
required |
grid_name
|
str
|
Grid identifier; the output variable is |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
ds with a |
extract_grid_vertices(grid)
¶
Extract 3D vertices from hemisphere grid cells.
Dispatches to a grid-type–specific extractor. The returned arrays are flat (not per-cell); use them directly for 3-D scatter plots.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Hemisphere grid instance. |
required |
Returns:
| Type | Description |
|---|---|
x_vertices, y_vertices, z_vertices : np.ndarray
|
Cartesian vertex coordinates on the unit sphere. |
grid_to_dataset(grid)
¶
Convert a HemiGrid to a unified xarray Dataset with vertices.
The returned Dataset carries cell centers, NaN-padded vertex arrays,
vertex counts, and solid angles, all indexed by cell_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Hemisphere grid instance. |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with dimensions |
Aggregation¶
Per-cell aggregation of VOD observations onto hemisphere grids.
Top-level entry points
aggregate_data_to_grid – single-statistic spatial aggregation
compute_percell_timeseries – chunked (cell × time) time-series
Analysis helpers
compute_global_average – observation-count–weighted global mean
compute_regional_average – same, restricted to a cell subset
analyze_diurnal_patterns – hourly groupby
analyze_spatial_patterns – time-averaged spatial field
Convenience wrappers
compute_hemisphere_percell – daily, full hemisphere
compute_zenith_percell – daily, θ ≤ 30°
CellAggregator
¶
Polars-based per-cell aggregation helpers.
aggregate_by_cell(df, value_var='VOD', method='mean')
staticmethod
¶
Aggregate values by cell_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Must contain |
required |
value_var
|
str
|
Column to aggregate. |
'VOD'
|
method
|
('mean', 'median', 'std', 'count')
|
Aggregation method. |
'mean'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Two-column DataFrame: |
aggregate_data_to_grid(data_ds, grid, value_var='VOD', cell_var='cell_id_equal_area_2deg', sid=None, time_range=None, stat='median')
¶
Aggregate VOD data across all timestamps and SIDs to per-cell statistics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_ds
|
Dataset
|
Full VOD dataset from the Icechunk store. |
required |
grid
|
GridData
|
Grid definition. |
required |
value_var
|
str
|
Name of the VOD variable. |
'VOD'
|
cell_var
|
str
|
Name of the cell-ID variable in data_ds. |
'cell_id_equal_area_2deg'
|
sid
|
list[str]
|
Satellite IDs to include. |
None
|
time_range
|
tuple
|
|
None
|
stat
|
('mean', 'median', 'std')
|
Statistic to compute per cell. |
'mean'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of length |
compute_percell_timeseries(data_ds, grid, value_var='VOD', cell_var='cell_id_equal_area_2deg', theta_range=None, phi_range=None, selected_sids=None, time_range=None, temporal_resolution='1D', chunk_days=21, min_obs_per_cell_time=1, stat='mean')
¶
Compute time series per cell with SID aggregation.
Processing is chunked over time to bound memory usage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_ds
|
Dataset
|
Full VOD dataset. |
required |
grid
|
GridData
|
Grid definition. |
required |
value_var
|
str
|
VOD variable name. |
'VOD'
|
cell_var
|
str
|
Cell-ID variable name. |
'cell_id_equal_area_2deg'
|
theta_range
|
tuple
|
|
None
|
phi_range
|
tuple
|
|
None
|
selected_sids
|
list[str]
|
Satellite IDs to include. |
None
|
time_range
|
tuple
|
|
None
|
temporal_resolution
|
str
|
Pandas/polars frequency string (e.g. |
'1D'
|
chunk_days
|
int
|
Days per processing chunk. |
21
|
min_obs_per_cell_time
|
int
|
Minimum SID observations per (cell, time-bin) to retain. |
1
|
stat
|
('mean', 'median')
|
Statistic to compute per (cell, time-bin), mirroring
:func: |
'mean'
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with dimensions |
compute_global_average(percell_ds)
¶
Compute observation-count–weighted global average from per-cell data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Output of :func: |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Variables: |
compute_regional_average(percell_ds, region_cells)
¶
Compute observation-count–weighted average for a cell subset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Output of :func: |
required |
region_cells
|
array - like
|
Cell IDs defining the region. |
required |
Returns:
| Type | Description |
|---|---|
DataArray
|
Weighted regional mean time series. |
analyze_diurnal_patterns(percell_ds)
¶
Compute hourly means from per-cell time series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Output of :func: |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Grouped by |
analyze_spatial_patterns(percell_ds)
¶
Compute time-averaged spatial field.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Output of :func: |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Time-averaged dataset. |
compute_hemisphere_percell(data_ds, grid, **kwargs)
¶
Daily per-cell time series for the full hemisphere.
compute_zenith_percell(data_ds, grid, **kwargs)
¶
Daily per-cell time series restricted to θ ≤ 30°.
Analysis¶
Filtering¶
Global (dataset-wide) outlier filters for gridded VOD data.
Classes:
| Name | Description |
|---|---|
Filter – abstract base; ``compute_mask`` / ``apply`` contract. |
|
ZScoreFilter – mean ± k·σ rejection. |
|
IQRFilter – Q1 – f·IQR / Q3 + f·IQR rejection. |
|
RangeFilter – hard min/max bounds. |
|
PercentileFilter – lower/upper percentile bounds. |
|
CustomFilter – user-supplied callable mask. |
|
FilterPipeline – sequential or combined multi-filter application. |
|
Convenience functions
create_zscore_filter – one-liner z-score filter.
create_range_filter – one-liner range filter.
Notes
- Filters never modify original data.
applyreturns a newxr.Datasetwith<var>_filtered_<n>andmask_<n>variables appended. - Both numpy and dask-backed arrays are supported; dask paths compute only the scalar statistics eagerly while the mask itself stays lazy.
Filter
¶
Bases: ABC
Base class for all filters. Filters NEVER modify original data.
__init__(name)
¶
Initialize the filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Filter name. |
required |
compute_mask(data, **kwargs)
abstractmethod
¶
Compute boolean mask (True = keep, False = remove).
apply(ds, var_name, output_suffix=None, **kwargs)
¶
Apply filter to ds, returning a copy with filtered variable added.
New variables
<var_name>_filtered_<suffix> : filtered data (NaN where masked).
mask_<suffix> : boolean keep-mask.
RangeFilter
¶
Bases: Filter
Filter values outside specified range.
__init__()
¶
Initialize the filter.
compute_mask(data, min_value=None, max_value=None, **kwargs)
¶
Compute range mask.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Input data. |
required |
min_value
|
float
|
Minimum allowed value. |
None
|
max_value
|
float
|
Maximum allowed value. |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Boolean mask (True = keep). |
PercentileFilter
¶
Bases: Filter
Filter values outside percentile range.
__init__()
¶
Initialize the filter.
compute_mask(data, lower=5.0, upper=95.0, **kwargs)
¶
Compute percentile mask.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Input data. |
required |
lower
|
float
|
Lower percentile (0–100). |
5.0
|
upper
|
float
|
Upper percentile (0–100). |
95.0
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Boolean mask (True = keep). |
CustomFilter
¶
Bases: Filter
Apply a user-supplied callable as filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Filter identifier. |
required |
func
|
callable
|
|
required |
SIDPatternFilter
¶
Bases: Filter
Filter dataset by GNSS system, frequency band, and tracking code.
Operates on the sid dimension of a dataset, where SIDs have the
format SV|Band|Code (e.g. G01|L1|C).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
system
|
str or None
|
GNSS system prefix to keep (e.g. |
None
|
band
|
str or None
|
Frequency band to keep (e.g. |
None
|
code
|
str or None
|
Tracking code to keep (e.g. |
None
|
Examples:
>>> filt = SIDPatternFilter(system="G", band="L1", code="C")
>>> ds_gps_l1c = filt.filter_dataset(ds)
compute_mask(data, **kwargs)
¶
Compute boolean mask along the sid dimension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Input data with a |
required |
Returns:
| Type | Description |
|---|---|
DataArray
|
Boolean mask (True = keep) broadcast to data shape. |
filter_dataset(ds)
¶
Filter dataset to matching SIDs by slicing the sid dimension.
Unlike apply (which masks values with NaN), this method drops
non-matching SIDs entirely — reducing the dataset size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Dataset with a |
required |
Returns:
| Type | Description |
|---|---|
Dataset or None
|
Filtered dataset, or |
FilterPipeline
¶
Manage multiple filters applied sequentially or combined.
Non-destructive: creates new DataArrays, never modifies originals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
var_name
|
str
|
Variable to filter (default: |
'VOD'
|
__init__(ds, var_name='VOD')
¶
Initialize the filter pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
var_name
|
str
|
Variable to filter. |
"VOD"
|
add_filter(filter_obj, **kwargs)
¶
Add filter to pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filter_obj
|
Filter or str
|
Filter instance or short name
( |
required |
**kwargs
|
Any
|
Parameters forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
FilterPipeline
|
Self (for chaining). |
apply(mode='sequential', output_name=None)
¶
Apply all filters in the pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
('sequential', 'combined')
|
|
'sequential'
|
output_name
|
str
|
Alias for the final filtered variable. |
None
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with filtered variables appended. |
summary()
¶
Return a human-readable summary of the pipeline.
create_zscore_filter(ds, var_name='VOD', threshold=3.0, suffix='zscore')
¶
One-liner z-score filter.
create_range_filter(ds, var_name='VOD', min_value=None, max_value=None, suffix='range')
¶
One-liner range filter.
Per-cell filtering¶
Per-cell outlier filters for gridded VOD data.
Unlike the global filters in :mod:~canvod.grids.analysis.filtering, these
operate independently on each grid cell, preserving spatial structure
while removing temporal outliers within cells.
Classes:
| Name | Description |
|---|---|
PerCellFilter – abstract base with auto cell-id detection. |
|
PerCellIQRFilter – per-cell IQR rejection. |
|
PerCellZScoreFilter – per-cell z-score rejection. |
|
PerCellRangeFilter – per-cell hard bounds. |
|
PerCellPercentileFilter – per-cell percentile bounds. |
|
PerCellFilterPipeline – sequential or combined multi-filter application. |
|
Convenience functions
create_per_cell_iqr_filter – one-liner per-cell IQR.
create_per_cell_zscore_filter – one-liner per-cell z-score.
PerCellFilter
¶
Bases: ABC
Base class for per-cell filtering operations.
Sub-classes implement :meth:compute_cell_mask for a single cell's
1-D data array; the base class handles iteration over cells,
auto-detection of the cell_id_* variable, and output assembly.
__init__(filter_name)
¶
Initialize the per-cell filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filter_name
|
str
|
Filter name. |
required |
compute_cell_mask(cell_data, **kwargs)
abstractmethod
¶
Return a boolean keep-mask for one cell's data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell_data
|
ndarray
|
1-D array of values for one cell across time. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean mask (True = keep). |
apply(ds, var_name='VOD', cell_id_var=None, output_suffix=None, min_observations=5, **kwargs)
¶
Apply per-cell filtering to ds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset (must contain a |
required |
var_name
|
str
|
Variable to filter. |
'VOD'
|
cell_id_var
|
str
|
Cell-ID variable name. Auto-detected from |
None
|
output_suffix
|
str
|
Suffix for output variables (default: |
None
|
min_observations
|
int
|
Minimum observations per cell required for filtering. |
5
|
**kwargs
|
Any
|
Forwarded to :meth: |
{}
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Copy of ds with |
PerCellIQRFilter
¶
Bases: PerCellFilter
Per-cell IQR outlier rejection.
Values outside [Q1 − factor·IQR, Q3 + factor·IQR] are removed
independently within each cell.
PerCellZScoreFilter
¶
Bases: PerCellFilter
Per-cell z-score outlier rejection.
Values with |z| > threshold are removed independently within each cell.
__init__()
¶
Initialize the filter.
compute_cell_mask(cell_data, threshold=3.0, **kwargs)
¶
Z-score mask for a single cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell_data
|
ndarray
|
1-D cell data. |
required |
threshold
|
float
|
Z-score threshold (default 3.0). |
3.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean mask. |
PerCellRangeFilter
¶
Bases: PerCellFilter
Per-cell hard-bound range filter.
Identical bounds applied to every cell.
__init__()
¶
Initialize the filter.
compute_cell_mask(cell_data, min_value=None, max_value=None, **kwargs)
¶
Range mask for a single cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell_data
|
ndarray
|
1-D cell data. |
required |
min_value
|
float
|
Minimum allowed value. |
None
|
max_value
|
float
|
Maximum allowed value. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean mask. |
PerCellPercentileFilter
¶
Bases: PerCellFilter
Per-cell percentile-bound filter.
Bounds are computed independently within each cell.
__init__()
¶
Initialize the filter.
compute_cell_mask(cell_data, lower=5.0, upper=95.0, **kwargs)
¶
Percentile mask for a single cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell_data
|
ndarray
|
1-D cell data. |
required |
lower
|
float
|
Lower percentile (0–100). |
5.0
|
upper
|
float
|
Upper percentile (0–100). |
95.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean mask. |
PerCellFilterPipeline
¶
Sequential or combined multi-filter pipeline for per-cell filtering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
var_name
|
str
|
Variable to filter (default: |
'VOD'
|
__init__(ds, var_name='VOD')
¶
Initialize the filter pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Input dataset. |
required |
var_name
|
str
|
Variable to filter. |
"VOD"
|
add_filter(filter_obj, **kwargs)
¶
Add a filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filter_obj
|
PerCellFilter or str
|
Filter instance or short name
( |
required |
**kwargs
|
Any
|
Parameters forwarded to the filter. |
{}
|
Returns:
| Type | Description |
|---|---|
PerCellFilterPipeline
|
Self (for chaining). |
apply(mode='sequential', output_name=None)
¶
Apply all filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
('sequential', 'combined')
|
|
'sequential'
|
output_name
|
str
|
Alias for the final filtered variable. |
None
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with filtered results. |
create_per_cell_iqr_filter(ds, var_name='VOD', factor=1.5, cell_id_var=None, min_observations=5)
¶
One-liner per-cell IQR filter.
create_per_cell_zscore_filter(ds, var_name='VOD', threshold=3.0, cell_id_var=None, min_observations=5)
¶
One-liner per-cell z-score filter.
Masking¶
Spatial masking for hemispherical grid cells.
Provides tools to create boolean masks for selecting subsets of grid cells based on geometric constraints, data quality, or custom criteria.
Classes:
| Name | Description |
|---|---|
SpatialMask – builder for boolean cell-selection masks. |
|
Convenience functions
create_hemisphere_mask – north / south / east / west mask.
create_elevation_mask – elevation-angle-based mask.
SpatialMask
¶
Create spatial masks for grid cells.
Masks are boolean arrays where True = include cell,
False = exclude cell. Multiple constraints can be combined
with AND or OR logic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Grid instance. |
required |
Examples:
>>> mask = SpatialMask(grid)
>>> mask.add_phi_range(0, np.pi) # Northern hemisphere
>>> mask.add_theta_range(0, np.pi / 3) # Exclude low elevations
>>> mask.add_quality_threshold('mean_snr', min_value=40)
>>> spatial_mask = mask.compute() # Returns boolean array
__init__(grid)
¶
Initialize the spatial mask builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Grid instance. |
required |
add_phi_range(phi_min, phi_max, degrees=False)
¶
Add azimuth angle constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
phi_min
|
float
|
Minimum azimuth angle. |
required |
phi_max
|
float
|
Maximum azimuth angle. |
required |
degrees
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Notes
Handles wraparound at 0°/360° correctly.
Example: phi_range(350, 10) includes both 350°–360° and 0°–10°.
add_theta_range(theta_min, theta_max, degrees=False)
¶
Add polar angle constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theta_min
|
float
|
Minimum polar angle (0 = zenith). |
required |
theta_max
|
float
|
Maximum polar angle (π/2 = horizon). |
required |
degrees
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Notes
theta = 0° is zenith (straight up), theta = 90° is horizon.
To exclude low elevations, use theta_max < 90°.
add_elevation_range(elev_min, elev_max, degrees=True)
¶
Add elevation angle constraint (complementary to theta).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elev_min
|
float
|
Minimum elevation angle (0 = horizon, 90 = zenith). |
required |
elev_max
|
float
|
Maximum elevation angle. |
required |
degrees
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Notes
elevation = 90° − theta. This is more intuitive for users
who think in elevation angles.
add_cell_ids(cell_ids)
¶
Include specific cell IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell_ids
|
list[int] or ndarray
|
Cell IDs to include. |
required |
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
add_exclude_cell_ids(cell_ids)
¶
Exclude specific cell IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cell_ids
|
list[int] or ndarray
|
Cell IDs to exclude. |
required |
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
add_quality_threshold(var_name, min_value=None, max_value=None)
¶
Add data quality threshold based on grid cell properties.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_name
|
str
|
Variable name in grid (e.g. |
required |
min_value
|
float
|
Minimum allowed value. |
None
|
max_value
|
float
|
Maximum allowed value. |
None
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If var_name doesn't exist in the grid DataFrame. |
add_boundary_cells(exclude=True)
¶
Include or exclude boundary cells.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exclude
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the grid does not have an |
Notes
Only works if the grid DataFrame contains an 'is_boundary'
column.
add_custom_mask(mask, name='custom')
¶
Add custom mask or mask-generating callable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mask
|
ndarray or callable
|
|
required |
name
|
str
|
Label for this mask (used in summary). |
'custom'
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the resulting array has wrong shape or dtype. |
Examples:
>>> custom = np.array([True, False, True, ...])
>>> mask.add_custom_mask(custom)
>>> def high_snr_north(grid):
... snr = grid.grid['mean_snr'].to_numpy()
... phi = grid.grid['phi'].to_numpy()
... return (snr > 40) & (phi < np.pi)
>>> mask.add_custom_mask(high_snr_north)
add_radial_sector(center_phi, sector_width, theta_min=0.0, theta_max=np.pi / 2, degrees=True)
¶
Add radial sector (wedge) mask.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center_phi
|
float
|
Center azimuth of sector. |
required |
sector_width
|
float
|
Full angular width of sector. |
required |
theta_min
|
float
|
Minimum polar angle (radial inner bound). |
0.0
|
theta_max
|
float
|
Maximum polar angle (radial outer bound). |
pi / 2
|
degrees
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
SpatialMask
|
Self for chaining. |
Examples:
>>> # Northern sector, 30° wide, excluding low elevations
>>> mask.add_radial_sector(center_phi=0, sector_width=30,
... theta_max=60, degrees=True)
compute(mode='AND')
¶
Compute final combined boolean mask.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
str
|
Combination mode:
|
'AND'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean array of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no masks have been added or mode is unknown. |
get_mask_summary()
¶
Get summary of all added masks.
Returns:
| Type | Description |
|---|---|
dict
|
Dictionary with mask names and per-mask / combined cell counts. |
clear()
¶
__repr__()
¶
Return the developer-facing representation.
Returns:
| Type | Description |
|---|---|
str
|
Representation string. |
create_hemisphere_mask(grid, hemisphere='north')
¶
Create mask for a cardinal hemisphere.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Grid instance. |
required |
hemisphere
|
str
|
One of |
'north'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean mask. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If hemisphere is not recognized. |
create_elevation_mask(grid, min_elevation=30.0, max_elevation=90.0)
¶
Create mask based on elevation angle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Grid instance. |
required |
min_elevation
|
float
|
Minimum elevation angle in degrees (default: 30°). |
30.0
|
max_elevation
|
float
|
Maximum elevation angle in degrees (default: 90°). |
90.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean mask. |
Weighting¶
Weighting strategies for spatial aggregation of hemispherical grid data.
Provides tools to calculate and combine different weighting schemes for computing weighted means across grid cells. Critical for unbiased spatial statistics when cells have different sizes or data quality.
Classes:
| Name | Description |
|---|---|
WeightCalculator – builder for combined spatial weights. |
|
Convenience functions
compute_uniform_weights – equal weight per cell.
compute_area_weights – solid-angle-only weights.
Notes
- Supported weight types:
solid_angle,observation_count,snr,sin_elevation,inverse_variance,custom. - Multiple weights are combined element-wise (multiply or add) and optionally normalized to sum to 1.
- Dask-backed datasets are handled efficiently: only scalar statistics are computed eagerly; masks stay lazy.
WeightCalculator
¶
Calculate and combine weights for spatial aggregation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Grid instance. |
required |
ds
|
Dataset or None
|
Dataset with data variables (required for data-dependent weights
such as |
None
|
Examples:
>>> weights = WeightCalculator(grid, vod_ds)
>>> weights.add_weight('solid_angle')
>>> weights.add_weight('observation_count', normalize=True)
>>> total_weights = weights.compute()
__init__(grid, ds=None)
¶
Initialize the weight calculator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
GridData
|
Grid instance. |
required |
ds
|
Dataset | None
|
Dataset with data variables for data-dependent weights. |
None
|
add_weight(weight_type, normalize=True, **kwargs)
¶
Add a weight component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weight_type
|
str
|
One of |
required |
normalize
|
bool
|
If |
True
|
**kwargs
|
Any
|
Weight-specific parameters (see individual |
{}
|
Returns:
| Type | Description |
|---|---|
WeightCalculator
|
Self for chaining. |
Examples:
>>> calc.add_weight('solid_angle')
>>> calc.add_weight('observation_count', var_name='VOD', normalize=True)
>>> calc.add_weight('custom', values=my_weights, normalize=False)
compute(combination='multiply', normalize_final=True)
¶
Compute final combined weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
combination
|
str
|
|
'multiply'
|
normalize_final
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Weight array of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no weights have been added or combination is unknown. |
get_weight_summary()
¶
Summary statistics for each weight component.
Returns:
| Type | Description |
|---|---|
dict
|
Nested dict keyed by weight type. |
remove_weight(weight_type)
¶
Remove a weight component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weight_type
|
str
|
Weight type to remove. |
required |
Returns:
| Type | Description |
|---|---|
WeightCalculator
|
Self for chaining. |
clear()
¶
__repr__()
¶
Return the developer-facing representation.
Returns:
| Type | Description |
|---|---|
str
|
Representation string. |
Solar geometry¶
Solar position calculations and corrections for VOD data.
Provides tools to compute solar positions and apply corrections to account for solar radiation effects on vegetation optical depth measurements.
Classes:
| Name | Description |
|---|---|
SolarPositionCalculator – solar zenith / azimuth and VOD correction. |
|
Convenience functions
compute_solar_zenith – quick zenith-only computation.
filter_daytime_data – mask nighttime observations.
Notes
- When pvlib is installed it is preferred for high-accuracy calculations. The built-in fallback uses NOAA algorithms (accuracy ~0.01° for 1800–2100).
- All public methods accept either
np.ndarrayofdatetime64orpd.DatetimeIndex.
SolarPositionCalculator
¶
Calculate solar positions for VOD corrections.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lat
|
float
|
Observer latitude in degrees (positive = North). |
required |
lon
|
float
|
Observer longitude in degrees (positive = East). |
required |
elevation
|
float
|
Elevation above sea level in meters (default: 0). |
0.0
|
use_pvlib
|
bool
|
If |
True
|
Examples:
>>> calc = SolarPositionCalculator(lat=40.0, lon=-105.0, elevation=1655)
>>> times = pd.date_range('2025-01-01', periods=24, freq='1H')
>>> zenith, azimuth = calc.compute_solar_position(times)
>>> corrected = calc.apply_solar_correction(vod_data, times)
__init__(lat, lon, elevation=0.0, use_pvlib=True)
¶
Initialize the solar position calculator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lat
|
float
|
Observer latitude in degrees. |
required |
lon
|
float
|
Observer longitude in degrees. |
required |
elevation
|
float
|
Elevation above sea level in meters. |
0.0
|
use_pvlib
|
bool
|
Whether to use pvlib if available. |
True
|
compute_solar_position(times)
¶
Compute solar zenith and azimuth angles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
ndarray or DatetimeIndex
|
Array of |
required |
Returns:
| Name | Type | Description |
|---|---|---|
solar_zenith |
ndarray
|
Solar polar angles in degrees (0° = directly overhead). |
solar_azimuth |
ndarray
|
Solar azimuth angles in degrees (0° = North, 90° = East). |
compute_solar_elevation(times)
¶
Compute solar elevation angle (complementary to zenith).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
ndarray or DatetimeIndex
|
Array of times. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Solar elevation angles in degrees (0° = horizon, 90° = overhead). |
is_daytime(times, twilight_angle=-6.0)
¶
Determine if times are during daytime.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
ndarray or DatetimeIndex
|
Array of times. |
required |
twilight_angle
|
float
|
Solar elevation threshold in degrees.
Common values: |
-6.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean array ( |
apply_solar_correction(data, method='normalize', reference_zenith=45.0)
¶
Apply solar correction to data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Input data with a time dimension ( |
required |
method
|
str
|
Correction method:
|
'normalize'
|
reference_zenith
|
float
|
Reference polar angle for normalization (degrees). |
45.0
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Solar-corrected data with correction metadata in attrs. |
compute_solar_bins(times, n_bins=12)
¶
Bin times by solar elevation angle.
Useful for solar-elevation-based composites instead of hour-of-day composites.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
ndarray or DatetimeIndex
|
Array of times. |
required |
n_bins
|
int
|
Number of solar elevation bins (range −20° to 90°). |
12
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Bin indices (0-based) for each time. |
get_sunrise_sunset(date)
¶
Compute sunrise and sunset times for a given date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
date
|
datetime
|
Date to compute sunrise/sunset for. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
sunrise |
Timestamp or None
|
Sunrise time in UTC, or |
sunset |
Timestamp or None
|
Sunset time in UTC, or |
__repr__()
¶
Return the developer-facing representation.
Returns:
| Type | Description |
|---|---|
str
|
Representation string. |
compute_solar_zenith(lat, lon, times)
¶
Quick computation of solar polar angles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lat
|
float
|
Observer latitude in degrees. |
required |
lon
|
float
|
Observer longitude in degrees. |
required |
times
|
ndarray or DatetimeIndex
|
Times to compute for. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Solar polar angles in degrees. |
filter_daytime_data(data, lat, lon, twilight_angle=-6.0)
¶
Filter data to include only daytime observations.
Nighttime values are set to NaN.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
Data with a time dimension ( |
required |
lat
|
float
|
Observer latitude in degrees. |
required |
lon
|
float
|
Observer longitude in degrees. |
required |
twilight_angle
|
float
|
Elevation threshold for daytime (degrees below horizon). |
-6.0
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Filtered data. |
Temporal analysis¶
Temporal analysis of gridded VOD data.
Weighted time-series computation, diurnal cycle analysis, and temporal statistics with optional solar-position correction.
Classes:
| Name | Description |
|---|---|
``TemporalAnalysis`` |
Main analysis class; binds a VOD dataset to a grid and exposes methods for aggregation, solar correction, diurnal binning, and basic plotting. |
Notes
- All spatial masks and weight arrays must be 1-D with length
grid.ncells. - When a
SolarPositionCalculatoris attached (via site_lat / site_lon), additional solar-corrected and solar-binned methods become available. - Plotting helpers are thin wrappers around
matplotlib; they return(fig, ax)so callers can continue customising the figure.
TemporalAnalysis
¶
Temporal analysis of gridded VOD data.
Binds a VOD dataset (with pre-assigned cell IDs) to a grid and exposes weighted aggregation, diurnal analysis, and plotting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vod_ds
|
Dataset
|
Dataset containing VOD data and a |
required |
grid
|
GridData
|
Grid instance (must expose |
required |
grid_name
|
str
|
Suffix for the cell-ID variable (e.g. |
required |
site_lat
|
float or None
|
Site latitude in degrees. Required for solar methods. |
None
|
site_lon
|
float or None
|
Site longitude in degrees. Required for solar methods. |
None
|
site_elevation
|
float
|
Site elevation in meters (default 0). |
0.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> analysis = TemporalAnalysis(vod_ds, grid, "htm_10deg")
>>> ts = analysis.compute_timeseries(aggregate="1D")
__init__(vod_ds, grid, grid_name, site_lat=None, site_lon=None, site_elevation=0.0)
¶
Initialize the temporal analysis helper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vod_ds
|
Dataset
|
VOD dataset containing cell IDs. |
required |
grid
|
GridData
|
Grid instance. |
required |
grid_name
|
str
|
Grid name suffix for cell IDs. |
required |
site_lat
|
float | None
|
Site latitude in degrees. |
None
|
site_lon
|
float | None
|
Site longitude in degrees. |
None
|
site_elevation
|
float
|
Site elevation in meters. |
0.0
|
compute_timeseries(var_name='VOD', spatial_mask=None, weights=None, aggregate='1D', min_cells=1)
¶
Compute a weighted time-series aggregated over space.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_name
|
str
|
Data variable to aggregate. |
'VOD'
|
spatial_mask
|
ndarray or None
|
Boolean mask of shape |
None
|
weights
|
ndarray or None
|
Cell weights of shape |
None
|
aggregate
|
str
|
Pandas-compatible frequency string for temporal resampling. |
'1D'
|
min_cells
|
int
|
Minimum unique cells required per time bin. |
1
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Variables: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If var_name is missing or mask/weight shapes are wrong. |
compute_timeseries_solar_corrected(var_name='VOD', spatial_mask=None, weights=None, aggregate='1D', min_cells=1, solar_correction='normalize', reference_zenith=45.0, daytime_only=False, twilight_angle=-6.0)
¶
Compute a time-series after applying a solar correction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_name
|
str
|
Data variable to correct and aggregate. |
'VOD'
|
spatial_mask
|
ndarray or None
|
Cell selection mask. |
None
|
weights
|
ndarray or None
|
Cell weights. |
None
|
aggregate
|
str
|
Temporal resampling frequency. |
'1D'
|
min_cells
|
int
|
Minimum cells per time bin. |
1
|
solar_correction
|
('normalize', 'residual', 'cos_correction')
|
Correction method passed to
:meth: |
'normalize'
|
reference_zenith
|
float
|
Reference zenith for normalization (degrees). |
45.0
|
daytime_only
|
bool
|
If |
False
|
twilight_angle
|
float
|
Solar-elevation threshold for daytime (degrees). |
-6.0
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Solar-corrected time-series with additional metadata attrs. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no solar calculator is configured. |
compute_diurnal_cycle(var_name='VOD', spatial_mask=None, weights=None, hour_bins=24, min_observations=10)
¶
Compute clock-time diurnal cycle (hour-of-day statistics).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_name
|
str
|
Data variable to bin. |
'VOD'
|
spatial_mask
|
ndarray or None
|
Cell selection mask. |
None
|
weights
|
ndarray or None
|
Cell weights. |
None
|
hour_bins
|
int
|
Number of equal-width hour bins over [0, 24). |
24
|
min_observations
|
int
|
Minimum observations required per bin. |
10
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
compute_diurnal_cycle_solar(var_name='VOD', spatial_mask=None, weights=None, n_solar_bins=12, min_observations=10)
¶
Diurnal cycle binned by solar elevation instead of clock time.
Accounts for seasonal variation in solar position, producing a more physically meaningful diurnal pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_name
|
str
|
Data variable to bin. |
'VOD'
|
spatial_mask
|
ndarray or None
|
Cell selection mask. |
None
|
weights
|
ndarray or None
|
Cell weights. |
None
|
n_solar_bins
|
int
|
Number of equal-width bins over [-20°, 90°]. |
12
|
min_observations
|
int
|
Minimum observations per bin. |
10
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If no solar calculator is configured. |
add_solar_metadata_to_timeseries(timeseries)
¶
Attach solar zenith, azimuth and elevation to a time-series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeseries
|
Dataset
|
Time-series dataset with an |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Copy with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no solar calculator is configured. |
plot_timeseries(timeseries, smooth_window=0, show_uncertainty=True, show_n_cells=False, ax=None, **style_kwargs)
¶
Plot a time-series with optional Savitzky-Golay smoothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeseries
|
Dataset
|
Output of :meth: |
required |
smooth_window
|
int
|
Savitzky-Golay window length (0 = off; forced odd internally). |
0
|
show_uncertainty
|
bool
|
Draw ±1 std band. |
True
|
show_n_cells
|
bool
|
Secondary y-axis showing cell count. |
False
|
ax
|
Axes or None
|
Axes to draw on; created if |
None
|
**style_kwargs
|
Any
|
|
{}
|
Returns:
| Type | Description |
|---|---|
fig, ax : plt.Figure, plt.Axes
|
|
plot_diurnal_cycle(diurnal, show_confidence=True, ax=None, **style_kwargs)
¶
Plot a clock-time diurnal cycle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
diurnal
|
Dataset
|
Output of :meth: |
required |
show_confidence
|
bool
|
Draw ±1 std band. |
True
|
ax
|
Axes or None
|
Axes to draw on; created if |
None
|
**style_kwargs
|
Any
|
|
{}
|
Returns:
| Type | Description |
|---|---|
fig, ax : plt.Figure, plt.Axes
|
|
plot_diurnal_cycle_comparison(diurnal_clock, diurnal_solar, figsize=(14, 6), **style_kwargs)
¶
Side-by-side clock-time vs solar-time diurnal cycle plots.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
diurnal_clock
|
Dataset
|
Output of :meth: |
required |
diurnal_solar
|
Dataset
|
Output of :meth: |
required |
figsize
|
tuple
|
Figure size. |
(14, 6)
|
**style_kwargs
|
Any
|
|
{}
|
Returns:
| Type | Description |
|---|---|
fig, axes : plt.Figure, np.ndarray of plt.Axes
|
|
Spatial analysis¶
Spatial analysis of gridded VOD data.
Per-cell statistical aggregation and basic comparative plotting for VOD datasets with pre-assigned cell IDs.
Classes:
| Name | Description |
|---|---|
``VODSpatialAnalyzer`` |
Computes per-cell temporal statistics and provides simple histogram-based comparisons between filtering variants. |
Notes
- Spatial visualization (hemisphere maps, 3-D projections) is
handled by the
canvod-vizpackage. This module is limited to the statistical aggregation and lightweight comparison plots that do not require a hemisphere renderer. compute_spatial_statisticsreturns both a grid-aligned array (lengthgrid.ncells, NaN for empty cells) and a compact patch-aligned array containing only cells with observations. Use the grid-aligned form for masking / weighting; use the patch-aligned form when passing data tocanvod-vizrenderers.
VODSpatialAnalyzer
¶
Per-cell spatial analysis of a VOD dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vod_data
|
Dataset
|
Dataset with a |
required |
grid
|
GridData
|
Grid instance (must expose |
required |
grid_name
|
str
|
Suffix for the cell-ID variable. |
'equal_area_2deg'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the expected cell-ID variable is missing. |
__init__(vod_data, grid, grid_name='equal_area_2deg')
¶
Initialize the spatial analyzer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vod_data
|
Dataset
|
Dataset with cell IDs. |
required |
grid
|
GridData
|
Grid instance. |
required |
grid_name
|
str
|
Grid name suffix for cell IDs. |
"equal_area_2deg"
|
compute_spatial_statistics(var_name='VOD', time_agg='mean')
¶
Compute per-cell temporal statistics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var_name
|
str
|
Data variable to aggregate over time. |
'VOD'
|
time_agg
|
('mean', 'std', 'count', 'median')
|
Aggregation function applied per cell. |
'mean'
|
Returns:
| Type | Description |
|---|---|
dict
|
Keys:
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If var_name is not in the dataset or time_agg is unrecognised. |
compare_filtering_methods(original_var='VOD', filtered_var='VOD_filtered', figsize=(16, 6), ax=None)
¶
Histogram comparison of original vs filtered VOD distributions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
original_var
|
str
|
Unfiltered variable name. |
'VOD'
|
filtered_var
|
str
|
Filtered variable name. |
'VOD_filtered'
|
figsize
|
tuple
|
Figure size when ax is |
(16, 6)
|
ax
|
tuple of two plt.Axes or None
|
Pre-existing axes pair. Created if |
None
|
Returns:
| Type | Description |
|---|---|
fig, axes : plt.Figure, np.ndarray of plt.Axes
|
|
Per-cell VOD analysis¶
Per-cell VOD analysis and plotting.
Statistical aggregation, diurnal dynamics, radial distributions, and theta-time heatmaps for per-cell VOD datasets. Handles single datasets or lists of datasets with configurable multi-dataset modes (separate vs averaged).
Classes:
| Name | Description |
|---|---|
``PerCellVODAnalyzer`` |
Main analysis class. Accepts one or more per-cell datasets
(each must expose |
Utility functions
extract_percell_stats – temporal statistic per cell.
percell_to_grid_counts – total observation counts per cell.
extract_percell_temporal_stats – range / trend / CV per cell.
extract_percell_coverage – data-coverage percentage per cell.
percell_to_grid_data – thin wrapper around
:func:extract_percell_stats.
Notes
- Per-cell datasets are expected to have dimensions
(cell, time)and variablescell_timeseries,cell_theta,cell_phi, and optionallycell_weightsandcell_counts. - Spatial visualization (hemisphere maps) lives in
canvod-viz.
PerCellVODAnalyzer
¶
Multi-dataset per-cell VOD analyzer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
datasets
|
xr.Dataset or list of xr.Dataset
|
Per-cell dataset(s). Each must contain |
required |
labels
|
list of str or None
|
Human-readable labels for each dataset. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If any dataset is missing required variables. |
__init__(datasets, labels=None)
¶
Initialize the analyzer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
datasets
|
Dataset | list[Dataset]
|
Per-cell dataset(s). |
required |
labels
|
list[str] | None
|
Labels for each dataset. |
None
|
extract_percell_stats(percell_ds, stat='median')
¶
Compute a single temporal statistic for every cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Per-cell dataset with a |
required |
stat
|
('mean', 'median', 'std')
|
Statistic to reduce across the |
"mean"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D array of length |
percell_to_grid_counts(percell_ds)
¶
Sum observation counts across time for each cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Per-cell dataset with a |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D array of total counts per cell. |
extract_percell_temporal_stats(percell_ds, stat='range')
¶
Compute a temporal-characteristic statistic for every cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Per-cell dataset with |
required |
stat
|
('range', 'trend', 'cv')
|
Statistic:
|
"range"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D array of length |
extract_percell_coverage(percell_ds)
¶
Compute the fraction of valid (non-NaN) observations per cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Per-cell dataset with |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D array of coverage percentages (0–100) per cell. |
percell_to_grid_data(percell_ds, stat='median')
¶
Thin wrapper around :func:extract_percell_stats.
Provided for symmetry with aggregate_data_to_grid workflows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
percell_ds
|
Dataset
|
Per-cell dataset. |
required |
stat
|
('mean', 'median', 'std')
|
Statistic to compute. |
"mean"
|
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D array compatible with grid visualization. |