aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/h3par.py
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-07 00:14:38 +0200
committergodosa <godosa@godosa.eu>2026-10-07 00:14:38 +0200
commit3443c1c65e9f1753e1e656b35d08416c1fa298f2 (patch)
tree4e43236f460145a4d75d1b4616dcb7aa6ef08f51 /h3par.py
downloadworldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.tar.gz
worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.zip
worldmap-viewer: initial public history
Diffstat (limited to 'h3par.py')
-rw-r--r--h3par.py50
1 files changed, 50 insertions, 0 deletions
diff --git a/h3par.py b/h3par.py
new file mode 100644
index 0000000..d1c55a5
--- /dev/null
+++ b/h3par.py
@@ -0,0 +1,50 @@
+"""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])))