예제 #1
0
    def test_hashmap_can_set_an_object_by_key(self):
        map = HashMap(10, hash, equal)
        key = {'hash': 1}

        map.set(key, 42)

        self.assertEqual(map.get(key), 42)
예제 #2
0
    def test_hashmap_when_a_hash_collision_occurs_get_checks_that_the_keys_are_equal(
            self):
        map = HashMap(10, hash, equal)
        key_1 = {'hash': 1}
        key_2 = {'hash': 1}
        key_3 = {'hash': 1}

        map.set(key_1, 'A')
        map.set(key_2, 'B')

        self.assertEqual(map.get(key_1), 'A')
        self.assertEqual(map.get(key_2), 'B')
        self.assertEqual(map.get(key_3), None)
예제 #3
0
    def test_hashmap_when_a_hash_collision_occurs_get_checks_that_the_keys_are_equal(
        self,
    ):
        map = HashMap(10, hash, equal)
        key_1 = {"hash": 1}
        key_2 = {"hash": 1}
        key_3 = {"hash": 1}

        map.set(key_1, "A")
        map.set(key_2, "B")

        self.assertEqual(map.get(key_1), "A")
        self.assertEqual(map.get(key_2), "B")
        self.assertEqual(map.get(key_3), None)
예제 #4
0
    def test_hashmap_the_hash_function_must_return_a_nonnegative_integer_but_can_be_greater_than_size(
            self):
        map = HashMap(10, hash, equal)
        key = {'hash': 11}

        self.assertEqual(map.get(key), None)
        self.assertEqual(map.set(key, 42), 42)
        self.assertEqual(map.get(key), 42)
예제 #5
0
    def test_hashmap_set_throws_an_error_when_full(self):
        # minimum size of 16
        map = HashMap(0, hash, equal)
        keys = list()

        for i in range(16):
            keys.append({'hash': i})
            map.set(keys[i], True)

        # replacing is okay
        for i in range(16):
            map.set(keys[i], True)

        with self.assertRaises(ValueError):
            map.set({'hash': 16}, True)
예제 #6
0
    def test_hashmap_set_returns_the_set_value(self):
        map = HashMap(10, hash, equal)
        key = {'hash': 1}

        self.assertEqual(map.set(key, 42), 42)
예제 #7
0
class Dedup(object):
    """
    Given a cut topology, combines duplicate arcs.
    """
    def __init__(self):
        pass

    def __call__(self, topology, *args, **kwargs):
        self.coordinates = topology["coordinates"]
        self.lines = topology["lines"].copy()
        self.rings = topology["rings"].copy()
        self.arc_count = len(self.lines) + len(self.rings)

        topology.pop("lines", None)
        topology.pop("rings", None)

        for l in self.lines:
            line = l
            while line.get("next", False):
                self.arc_count += 1
                line = line["next"]

        for r in self.rings:
            ring = r
            while ring.get("next", False):
                self.arc_count += 1
                ring = ring["next"]

        self.arcs_by_end = HashMap(self.arc_count * 2 * 1.4, hash_point,
                                   equal_point)
        self.arcs = list()

        for l in self.lines:
            line = l

            while line:
                self.dedup_line(line)
                line = line.get("next", False)

        for r in self.rings:
            ring = r
            # arc is no longer closed

            if ring.get("next", False):
                while ring:
                    self.dedup_line(ring)
                    ring = ring.get("next", False)
            else:
                self.dedup_ring(ring)

        topology["arcs"] = self.arcs.copy()
        return topology

    def dedup_line(self, arc):
        # Does this arc match an existing arc in order?
        start_point = self.coordinates[arc[0]]
        start_arcs = self.arcs_by_end.get(start_point)

        if start_arcs:
            for start_arc in start_arcs:
                if self.equal_line(start_arc, arc):
                    arc[0] = start_arc[0]
                    arc[1] = start_arc[1]
                    return

        # Does this arc match an existing arc in reverse order?
        end_point = self.coordinates[arc[1]]
        end_arcs = self.arcs_by_end.get(end_point)

        if end_arcs:
            for end_arc in end_arcs:
                if self.reverse_equal_line(end_arc, arc):
                    arc[0] = end_arc[1]
                    arc[1] = end_arc[0]
                    return

        if start_arcs:
            start_arcs.append(arc)
        else:
            self.arcs_by_end.set(start_point, [arc])

        if end_arcs:
            end_arcs.append(arc)
        else:
            self.arcs_by_end.set(end_point, [arc])

        self.arcs.append(arc)

    def dedup_ring(self, arc):
        # Does this arc match an existing line in order, or reverse order?
        # Rings are closed, so their start point and end point is the same.
        end_point = self.coordinates[arc[0]]
        end_arcs = self.arcs_by_end.get(end_point)

        if end_arcs:
            for end_arc in end_arcs:
                if self.equal_ring(end_arc, arc):
                    arc[0] = end_arc[0]
                    arc[1] = end_arc[1]
                    return

                if self.reverse_equal_ring(end_arc, arc):
                    arc[0] = end_arc[1]
                    arc[1] = end_arc[0]
                    return

        # Otherwise, does this arc match an existing ring in order, or reverse order?
        end_point = self.coordinates[arc[0] + self.find_minimum_offset(arc)]
        end_arcs = self.arcs_by_end.get(end_point)

        if end_arcs:
            for end_arc in end_arcs:
                if self.equal_ring(end_arc, arc):
                    arc[0] = end_arc[0]
                    arc[1] = end_arc[1]
                    return

                if self.reverse_equal_ring(end_arc, arc):
                    arc[0] = end_arc[1]
                    arc[1] = end_arc[0]
                    return

        if end_arcs:
            end_arcs.append(arc)
        else:
            self.arcs_by_end.set(end_point, [arc])

        self.arcs.append(arc)

    def equal_line(self, arc_a, arc_b):
        i_a, j_a = arc_a[0], arc_a[1]
        i_b, j_b = arc_b[0], arc_b[1]

        if i_a - j_a != i_b - j_b:
            return False

        while i_a <= j_a:
            if not equal_point(self.coordinates[i_a], self.coordinates[i_b]):
                return False
            else:
                i_a += 1
                i_b += 1

        return True

    def reverse_equal_line(self, arc_a, arc_b):
        i_a, j_a = arc_a[0], arc_a[1]
        i_b, j_b = arc_b[0], arc_b[1]

        if i_a - j_a != i_b - j_b:
            return False

        while i_a <= j_a:
            if not equal_point(self.coordinates[i_a], self.coordinates[j_b]):
                return False
            else:
                i_a += 1
                j_b -= 1

        return True

    def equal_ring(self, arc_a, arc_b):
        i_a, j_a = arc_a[0], arc_a[1]
        i_b, j_b = arc_b[0], arc_b[1]
        n = j_a - i_a

        if n != j_b - i_b:
            return False

        k_a = self.find_minimum_offset(arc_a)
        k_b = self.find_minimum_offset(arc_b)

        for i in range(n):
            if not equal_point(
                    self.coordinates[i_a + (i + k_a) % n],
                    self.coordinates[i_b + (i + k_b) % n],
            ):
                return False

        return True

    def reverse_equal_ring(self, arc_a, arc_b):
        i_a, j_a = arc_a[0], arc_a[1]
        i_b, j_b = arc_b[0], arc_b[1]
        n = j_a - i_a

        if n != j_b - i_b:
            return False

        k_a = self.find_minimum_offset(arc_a)
        k_b = n - self.find_minimum_offset(arc_b)

        for i in range(n):
            if not equal_point(
                    self.coordinates[i_a + (i + k_a) % n],
                    self.coordinates[j_b - (i + k_b) % n],
            ):
                return False

        return True

    def find_minimum_offset(self, arc):
        # Rings are rotated to a consistent, but arbitrary, start point.
        # This is necessary to detect when a ring and a rotated copy are dupes.
        start, end = arc[0], arc[1]
        mid = start
        minimum = mid
        minimum_point = self.coordinates[mid]

        mid += 1
        while mid < end:
            point = self.coordinates[mid]

            if (point[0] < minimum_point[0] or point[0] == minimum_point[0]
                    and point[1] < minimum_point[1]):
                minimum = mid
                minimum_point = point

            mid += 1

        return minimum - start
예제 #8
0
class Topology(object):
    """
    Constructs the TopoJSON Topology for the specified hash of features.
    Each object in the specified hash must be a GeoJSON object,
    meaning FeatureCollection, a Feature or a geometry object.
    """

    def __init__(self):
        self.bounding_box = bounds.BoundingBox()
        self.cut = cut.Cut()
        self.dedup = dedup.Dedup()
        self.delta = delta.Delta()
        self.extract = extract.Extract()
        self.geometry = geometry.Geometry()
        self.prequantize = prequantize.Prequantize()

    def __call__(self, objects, quantization=-1):
        self.index_geometry_type = {
            "GeometryCollection": self._geometry_collection_call,
            "LineString": self._line_string_call,
            "MultiLineString": self._multi_line_string_call,
            "Polygon": self._polygon_call,
            "MultiPolygon": self._multi_polygon_call,
        }

        objects = self.geometry(objects)
        self.bbox = self.bounding_box(objects)
        self.transform = (
            quantization > 0
            and self.bbox
            and self.prequantize(objects, self.bbox, quantization)
        )
        self.topology = self.dedup(self.cut(self.extract(objects)))
        self.coordinates = self.topology["coordinates"]
        self.index_by_arc = HashMap(
            len(self.topology["arcs"]) * 1.4, self.hash_arc, self.equal_arc
        )

        objects = self.topology["objects"]
        if self.bbox is not None:
            self.topology["bbox"] = self.bbox

        self.topology["arcs"] = list(
            map(
                lambda arc, i: self._slice(arc, i),
                self.topology["arcs"],
                range(len(self.topology["arcs"])),
            )
        )

        self.topology.pop("coordinates", None)
        self.coordinates = None

        for k in objects:
            self.index_geometry(objects[k])

        if self.transform:
            self.topology["transform"] = self.transform
            self.topology["arcs"] = self.delta(self.topology["arcs"])

        return self.topology

    def _slice(self, arc, i):
        self.index_by_arc.set(arc, i)
        return self.coordinates[arc[0] : arc[1] + 1]

    def _geometry_collection_call(self, o):
        list(map(self.index_geometry, o["geometries"]))

    def _line_string_call(self, o):
        o["arcs"] = self.index_arcs(o["arcs"])

    def _multi_line_string_call(self, o):
        o["arcs"] = list(map(self.index_arcs, o["arcs"]))

    def _polygon_call(self, o):
        o["arcs"] = list(map(self.index_arcs, o["arcs"]))

    def _multi_polygon_call(self, o):
        o["arcs"] = list(map(self.index_multi_arcs, o["arcs"]))

    def index_geometry(self, geometry):
        function = self.index_geometry_type.get(geometry["type"], None)
        if geometry and function:
            function(geometry)

    def index_arcs(self, arc):
        indexes = list()

        while arc:
            index = self.index_by_arc.get(arc)
            indexes.append(index if arc[0] < arc[1] else ~index)
            arc = arc.get("next", False)

        return indexes

    def index_multi_arcs(self, arcs):
        return list(map(self.index_arcs, arcs))

    @staticmethod
    def hash_arc(arc):
        i, j = arc[0], arc[1]
        if j < i:
            t = i
            i = j
            j = t

        return i + 31 * j

    @staticmethod
    def equal_arc(arc_a, arc_b):
        i_a, j_a = arc_a[0], arc_a[1]
        i_b, j_b = arc_b[0], arc_b[1]

        if j_a < i_a:
            t = i_a
            i_a = j_a
            j_a = t

        if j_b < i_b:
            t = i_b
            i_b = j_b
            j_b = t

        return i_a == i_b and j_a == j_b