diff --git a/python/its-quadkeys/its_quadkeys/quadkeys.py b/python/its-quadkeys/its_quadkeys/quadkeys.py index 20c408e0..35662307 100644 --- a/python/its-quadkeys/its_quadkeys/quadkeys.py +++ b/python/its-quadkeys/its_quadkeys/quadkeys.py @@ -49,6 +49,8 @@ def __init__( f"cannot create a QuadKey from a {type(quadkey)}={quadkey!s}" ) qk = quadkey.replace(separator, "") + if not qk: + raise ValueError("QuadKey can't be zero-length") err = "".join(set([q for q in qk if q not in "0123"])) if err: raise ValueError(f"QuadKey can oly contain '0123', not any of '{err}'") @@ -87,7 +89,12 @@ def make_shallower(self, depth: int): new_depth = max(1, len(self.quadkey) + depth) else: new_depth = min(len(self.quadkey), depth) - return QuadKey(self.quadkey[:depth]) + return QuadKey(self.quadkey[:new_depth]) + + def root(self): + """Returns the QuadKey immediately shallower, or None if this QuadKey + is already the shallowest.""" + return None if len(self.quadkey) == 1 else self.make_shallower(-1) def split(self, *, depth: int = None, extra_depth: int = None): """Split this QuadKey into an extra_depth-deeper QuadZone""" @@ -293,7 +300,7 @@ def __south_east_of_s(q: str): class QuadZone: - def __init__(self, *args: list[QuadKey | str | list[QuadKey | str]]): + def __init__(self, *args: QuadKey | str | list[QuadKey | str]): """Create a new QuadZone from an iterable of QuadKeys""" self.quadkeys = set() for arg in args: @@ -383,11 +390,13 @@ def optimise(self): continue # Are this QuadKey and the following three making a super - # QuadKey? I.e. do we have root0, root1, root2, and root3? + # QuadKey? I.e. do we have 'root0', 'root1', 'root2', and + # 'root3', with a non-empty 'root' (issue #130) qk_depth = quadkey.depth() - root = quadkey.make_shallower(-1) + root = quadkey.root() if ( - to_merge[0] == root + "1" + root + and to_merge[0] == root + "1" and to_merge[1] == root + "2" and to_merge[2] == root + "3" ): diff --git a/python/its-quadkeys/quadkeys-test b/python/its-quadkeys/quadkeys-test index 561e0c43..a05f95ee 100755 --- a/python/its-quadkeys/quadkeys-test +++ b/python/its-quadkeys/quadkeys-test @@ -52,6 +52,35 @@ def test_quadkey(): "1203021330", "1203021331", "1203021332", "1203021333", # fmt: on ] + expected_shallowers = [ + # fmt: off + (0, "12030213"), + (-1, "1203021"), + (-2, "120302"), + (-5, "120"), + (-100, "1"), + (1, "1"), + (2, "12"), + (5, "12030"), + (100, "12030213"), + # fmt: on + ] + expected_roots = [ + # fmt: off + ("123123", "12312"), + ("12", "1"), + ("1", None), + # fmt: on + ] + + print("QuadKey empty: ", end="", flush=True) + try: + _ = its_quadkeys.QuadKey('') + except ValueError: + pass # We expected that + else: + raise FailedError("QuadKey cannot be empty") from None + print("OK") print(f"QuadKey in: ", end="", flush=True) if qk_in not in qk: @@ -76,6 +105,24 @@ def test_quadkey(): check_zone(qk_split_z, expected_split_z_2) print(f"OK") + print(f"QuadKey shallower: ", end="", flush=True) + for depth, expected in expected_shallowers: + qk_shallow = qk.make_shallower(depth) + if qk_shallow != expected: + raise FailedError( + f"with QuadKey '{qk}' for depth {depth}, expecting '{expected}', got '{qk_shallow}'" + ) + print(f"OK") + + print(f"QuadKey root: ", end="", flush=True) + for qk_s, expected in expected_roots: + qk_r = its_quadkeys.QuadKey(qk_s).root() + if qk_r != expected: + raise FailedError( + f"for QuadKey '{qk_s}', expecting root '{expected}', got '{qk_r}'" + ) + print("OK") + print(f"QuadKey neighbours: ", end="", flush=True) nghbs = qk.neighbours() if nghbs._asdict() != expected_nghbs: @@ -357,6 +404,20 @@ def test_quadzone(): check_zone(z2, expected_xor) print("OK") + earth = its_quadkeys.QuadZone("0", "1", "2", "3") + print("QuadZone Whole-Earth optimise: ", end="", flush=True) + earth2 = its_quadkeys.QuadZone("0", "1", "2", "3") + earth2.optimise() + check_zone(earth2, earth) + print("OK") + + print("QuadZone Whole-Earth 2 optimise: ", end="", flush=True) + earth2_lst = ["0", "1", "2", "30", "31", "32", "330", "331", "332", "333"] + earth2 = its_quadkeys.QuadZone(earth2_lst) + earth2.optimise() + check_zone(earth2, earth) + print("OK") + def check_zone(some, expected): some_l = list(some)