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)
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)
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)
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)
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)
def test_hashmap_set_returns_the_set_value(self): map = HashMap(10, hash, equal) key = {'hash': 1} self.assertEqual(map.set(key, 42), 42)
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
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