Skip to content

Tiling

tiling

Split the points of one time slice along the source's own horizontal tiling.

A source stores its fields in tiles (latitude bands of Blosc blocks for ARCO-ERA5, square tiles for Open-Meteo). The points of one hour, possibly from many flights, occupy some of them. Occupied tiles that touch form one component; each component becomes its own cube, so the empty tiles between two distant groups of flights are never read, and no threshold has to be tuned: the source's tiling decides.

Tiling dataclass

Regular tiling of a grid: tile (i, j) starts at lat0 + i * lat_step.

lon_step=None means tiles span every longitude (ARCO-ERA5 bands). merge_adjacent=False groups points by their own tile, keeping long connected trajectories from requesting large mostly empty rectangles.

Source code in packages/barograph-met/src/baro/met/tiling.py
@dataclass(frozen=True)
class Tiling:
    """Regular tiling of a grid: tile ``(i, j)`` starts at ``lat0 + i * lat_step``.

    ``lon_step=None`` means tiles span every longitude (ARCO-ERA5 bands).
    ``merge_adjacent=False`` groups points by their own tile, keeping long
    connected trajectories from requesting large mostly empty rectangles.
    """

    lat0: float
    lat_step: float
    lon0: float = -180.0
    lon_step: float | None = None
    merge_adjacent: bool = True

    def __post_init__(self) -> None:
        if self.lat_step <= 0 or (self.lon_step is not None and self.lon_step <= 0):
            raise ValueError("tile steps must be positive")

    @property
    def n_cols(self) -> int:
        """Number of tile columns around the globe."""
        return 1 if self.lon_step is None else int(np.ceil(360.0 / self.lon_step - 1e-9))
n_cols property

Number of tile columns around the globe.

partition(lat, lon, margin, tiling)

Component label (0, 1, ...) of every point, from the tiles within margin of it.

Components are ordered by their first tile (south to north, then west to east); a point near a tile edge marks both tiles, so the neighbours its interpolation needs are always in its own component. With merge_adjacent=False, labels instead identify each point's own tile. The caller must retain its normal domain margin and grid neighbours when fetching each group.

Source code in packages/barograph-met/src/baro/met/tiling.py
def partition(
    lat: NDArray[np.float64],
    lon: NDArray[np.float64],
    margin: float,
    tiling: Tiling | None,
) -> NDArray[np.int64]:
    """Component label (0, 1, ...) of every point, from the tiles within ``margin`` of it.

    Components are ordered by their first tile (south to north, then west to
    east); a point near a tile edge marks both tiles, so the neighbours its
    interpolation needs are always in its own component.
    With ``merge_adjacent=False``, labels instead identify each point's own
    tile. The caller must retain its normal domain margin and grid neighbours
    when fetching each group.
    """
    if tiling is None or lat.size == 0:
        return np.zeros(lat.size, dtype=np.int64)
    if not tiling.merge_adjacent:
        # The caller still expands each group's domain by its requested margin.
        # Assigning only the point's own tile avoids merging overlapping halos.
        cells = _tile_ranges(lat, lon, 0.0, tiling)[:, [0, 2]]
        _, labels = np.unique(cells, axis=0, return_inverse=True)
        return np.asarray(labels, dtype=np.int64)
    rects, inverse = np.unique(_tile_ranges(lat, lon, margin, tiling), axis=0, return_inverse=True)
    sets = _UnionFind()
    ncols = tiling.n_cols
    firsts = [sets.add_rect(r0, r1, c0, c1, ncols) for r0, r1, c0, c1 in rects.tolist()]
    sets.link_neighbours(ncols)
    label = sets.labels()
    per_rect = np.array([label[sets.find(cell)] for cell in firsts], dtype=np.int64)
    return per_rect[inverse.reshape(-1)]