"""Per-cell h3 calls over millions of cells, split across worker processes (h3 is a C library called once per cell from Python: the loop, not the math, is the cost). Same values as the serial loops, in the same order.""" import os import h3.api.basic_int as h3 import numpy as np MIN_PARALLEL = 200_000 # fewer cells: the serial loop beats starting workers def rings(ids): """Neighbours of each cell (h3.grid_ring order), flat, with per-cell counts (pentagons have five).""" ids = np.asarray(ids, dtype=np.uint64) counts = np.empty(len(ids), np.int64) def walk(): # streamed: no list of millions of Python ints for k, c in enumerate(ids.tolist()): r = h3.grid_ring(c, 1) counts[k] = len(r) yield from r flat = np.fromiter(walk(), dtype=np.uint64) return flat, counts def centres_areas(ids): """(lat, lon) degrees (n, 2) and areas in rad² (n,) of the cells.""" ids = np.asarray(ids, dtype=np.uint64) ll = np.fromiter((v for c in ids.tolist() for v in h3.cell_to_latlng(c)), dtype=np.float64, count=2 * len(ids)) area = np.fromiter((h3.cell_area(c, unit="rads^2") for c in ids.tolist()), dtype=np.float64, count=len(ids)) return ll.reshape(-1, 2), area def workers() -> int: v = os.environ.get("MAPVIEW_H3_WORKERS") if v is not None: return max(0, int(v)) return max(0, min(6, (os.cpu_count() or 2) - 2)) def run(fn, ids): """fn(ids) (a function of this module returning arrays per cell, or (flat, counts)), split over workers.""" ids = np.asarray(ids, dtype=np.uint64) w = workers() if w < 2 or len(ids) < MIN_PARALLEL: return fn(ids) import multiprocessing as mp chunks = np.array_split(ids, w * 2) with mp.get_context("fork").Pool(w) as pool: # fork, as tiles' and export's pools parts = pool.map(fn, chunks) return tuple(np.concatenate([p[i] for p in parts]) for i in range(len(parts[0])))