def test_edge_property(self): """ Small experimental test to verify whether the Frechet Distance between any two edges is defined my the maximum distance between both pairs of endpoints. """ spacing = 0.5 fixed = Edge2D(Point2D(0.0, 0.0), Point2D(0.0, 1.0)).get_steiner_edge(spacing) u, v = fixed.get_spine() def almost_equal(estimate, real): return real + 1 >= estimate >= real - 1 for i in range(0, 10000): p1 = Point2D(float(randint(-20, 20)), float(randint(-20, 20))) p2 = Point2D(float(randint(-20, 20)), float(randint(-20, 20))) # Ensure points aren't the same if p1 == p2: p2 = Point2D(p2.x + 0.1, p2.y) rand_edge = Edge2D(p1, p2).get_steiner_edge(spacing) x, y = rand_edge.get_spine() r = min( max(np.linalg.norm(x.v - u.v), np.linalg.norm(y.v - v.v)), max(np.linalg.norm(y.v - u.v), np.linalg.norm(x.v - v.v)) ) frechet_estimate = discrete_frechet(fixed, rand_edge) assert almost_equal(r, frechet_estimate)
def test_edge(self): curve = Edge2D(Point2D(-5.0, 1.0), Point2D(-4.0, 4.0)) e = Edge2D(Point2D(-3.0, 1.0), Point2D(-3.0, 3.0)) grid = FrechetGrid2D(curve, self.error) real = discrete_frechet(e.get_steiner_curve(STEINER_SPACING), curve.get_steiner_curve(STEINER_SPACING)) estimate = grid.approximate_frechet(e) # Test for (1 + epsilon) property of grid estimate assert estimate <= real or \ real <= (1 + self.error) * estimate
def test_query_trivial_curve(self): tree = CurveRangeTree2D( PolygonalCurve2D( [Point2D(0.0, 0.0), Point2D(3.0, 0.0), Point2D(3.0, 3.0)]), self.error, self.delta) # Create query parameters q_edge = Edge2D(Point2D(0.0, -1.0), Point2D(3.0, -1.0)) x = Point2D(0.25, 0.0) x_edge = Edge2D(Point2D(0.0, 0.0), Point2D(3.0, 0.0)) y = Point2D(3.0, 2.5) y_edge = Edge2D(Point2D(3.0, 0.0), Point2D(3.0, 3.0)) assert tree.is_approximate(q_edge, x, y, x_edge, y_edge)
def __init_distances(self, curve): distances = dict() for p_prime in self.grid_u.points: distances[str(p_prime)] = dict() for q_prime in self.grid_v.points: distances[str(p_prime)][str(q_prime)] = \ discrete_frechet(Edge2D(p_prime, q_prime).get_steiner_curve(STEINER_SPACING), curve.get_steiner_curve(STEINER_SPACING)) return distances
def test_query_square_curve(self): tree = CurveRangeTree2D( PolygonalCurve2D([ Point2D(0.0, 0.0), Point2D(5.0, 0.0), Point2D(5.0, 5.0), Point2D(1.0, 5.0), Point2D(1.0, 1.0), Point2D(4.0, 1.0), Point2D(4.0, 4.0), Point2D(2.0, 4.0), Point2D(2.0, 2.0), Point2D(3.0, 2.0), Point2D(3.0, 3.0) ]), self.error, self.delta) # Create query parameters x = Point2D(2.5, 0.0) x_edge = Edge2D(Point2D(0.0, 0.0), Point2D(5.0, 0.0)) y = Point2D(3.0, 2.5) y_edge = Edge2D(Point2D(3.0, 2.0), Point2D(3.0, 3.0)) # Query various edges against this tree q_edge = Edge2D(Point2D(2.5, -2.0), Point2D(5.5, -0.5)) assert tree.is_approximate(q_edge, x, y, x_edge, y_edge) q_edge = Edge2D(Point2D(-1.1, 5.0), Point2D(-1.1, 1)) assert not tree.is_approximate(q_edge, x, y, x_edge, y_edge) q_edge = Edge2D(Point2D(1.0, 2.5), Point2D(5.0, 2.5)) assert not tree.is_approximate(q_edge, x, y, x_edge, y_edge) q_edge = Edge2D(Point2D(0.0, 0.0), Point2D(5.0, 5.0)) assert not tree.is_approximate(q_edge, x, y, x_edge, y_edge)
def test_small_float_values(self): tree = CurveRangeTree2D( PolygonalCurve2D([ Point2D(0.0, 0.0), Point2D(1.0, 0.0), Point2D(0.0, -0.01), Point2D(1.0, -0.01), Point2D(0.0, -0.02), Point2D(1.0, -0.02) ]), self.error, 0.55) # Create query parameters q_edge = Edge2D(Point2D(0.0, 0.0), Point2D(1.0, -0.02)) x = Point2D(0.0, 0.0) x_edge = Edge2D(Point2D(0.0, 0.0), Point2D(1.0, 0.0)) y = Point2D(1.0, -0.02) y_edge = Edge2D(Point2D(0.0, -0.02), Point2D(1.0, -0.02)) assert tree.is_approximate(q_edge, x, y, x_edge, y_edge) q_edge = Edge2D(Point2D(2.2, 0.0), Point2D(2.2, -0.02)) assert not tree.is_approximate(q_edge, x, y, x_edge, y_edge)
def __init__(self, curve, error): assert 0 < error <= 1, 'Error rate specified must be greater than 0 and at most 1.' self.__u, self.__v = curve.get_spine() self.__steiner_curve = curve.get_steiner_curve(STEINER_SPACING) self.__L = discrete_frechet( Edge2D(self.__u, self.__v).get_steiner_curve(STEINER_SPACING), curve.get_steiner_curve(STEINER_SPACING)) self.__error = error self.grid_u = ExponentialGrid2D(self.__u, error, error * self.__L / 2, self.__L / error) \ if self.__L != 0 else None self.grid_v = ExponentialGrid2D(self.__v, error, error * self.__L / 2, self.__L / error) \ if self.__L != 0 else None self.distances = self.__init_distances( curve) if self.__L != 0 else None
def find_frechet_bottleneck(self, q_edge, subpaths): # Step 2: Partition q_edge and compute partitioning point sets partitions = list() pi = q_edge.sub_divide(self.__error * self.__delta / 3) for subpath in subpaths[1:]: dag_points = Edge2D.partition(pi, subpath.curve.get_point(0), 2 * self.__delta) if len(dag_points) > 0: partitions.append(dag_points) # Step 3: Construct the Directed Acyclic Graph dag = DirectedAcyclicGraph() for i in range(0, len(partitions) - 1): j = i + 1 for u in partitions[i]: for v in partitions[j]: if u == v: continue elif u != q_edge.p2 and v.is_on_edge(Edge2D(u, q_edge.p2)): dag.add_edge( u, v, subpaths[i + 1].grid.approximate_frechet( Edge2D(u, v))) if len(partitions) > 0: for v in partitions[0]: if v == q_edge.p1: continue dag.add_edge( q_edge.p1, v, subpaths[0].grid.approximate_frechet(Edge2D(q_edge.p1, v))) for u in partitions[len(partitions) - 1]: if u == q_edge.p2: continue dag.add_edge( u, q_edge.p2, subpaths[len(partitions) - 1].grid.approximate_frechet( Edge2D(u, q_edge.p2))) else: dag.add_edge( q_edge.p1, q_edge.p2, subpaths[0].grid.approximate_frechet( Edge2D(q_edge.p1, q_edge.p2))) dag.add_edge( q_edge.p1, q_edge.p2, subpaths[len(partitions) - 1].grid.approximate_frechet( Edge2D(q_edge.p1, q_edge.p2))) # Step 4: Find the heaviest weighted edge on the bottleneck path of the DAG delta_prime = dag.bottleneck_path_weight(q_edge.p1, q_edge.p2) return delta_prime <= (1 + self.__error) * self.__delta
def is_approximate(self, q_edge, x, y, x_node, y_node): # Assume tree node data stores Point2D objects x_edge = Edge2D(x_node.point, x_node.parent.point) y_edge = Edge2D(y_node.point, y_node.parent.point) lca = self.tree.lowest_common_ancestor(x_node, y_node) paths = self.__find_decomposed_curves(x_node, lca) + self.__find_decomposed_curves(y_node, lca)[::-1] subpaths = list() for i in range(0, len(paths)): path = paths[i] curve_tree = self.path_trees.get(str(path)) end = path.get_point(-1) if i == 0: subpaths += curve_tree.partition_path(x, end, x_edge, Edge2D(path.get_point(-2), end)) elif i == len(paths) - 1: subpaths += curve_tree.partition_path(y, end, y_edge, Edge2D(path.get_point(-2), end)) else: start = path.get_point(0) subpaths += curve_tree.partition_path(start, end, Edge2D(start, path.get_point(1)), Edge2D(path.get_point(-2), end)) return self.path_trees.values()[0].find_frechet_bottleneck(q_edge, subpaths)
def partition_path(self, x, y, x_edge, y_edge): # Assumes x located on the left side of the path w.r.t. y x_node = self.__find_node(self.root, x_edge) y_node = self.__find_node(self.root, y_edge) # Assumes tree has already been decomposed lca = self.lowest_common_ancestor(x_node, y_node) # noinspection PyUnreachableCode def __walk_left(node, edge): if node.is_leaf(): yield node elif node.curve.is_in_left_curve(edge): for n in __walk_left(node.left, edge): yield n yield node.right elif node.curve.is_in_right_curve(edge): for n in __walk_left(node.right, edge): yield n else: return yield # noinspection PyUnreachableCode def __walk_right(node, edge): if node.is_leaf(): yield node elif node.curve.is_in_left_curve(edge): for n in __walk_right(node.left, edge): yield n elif node.curve.is_in_right_curve(edge): for n in __walk_right(node.right, edge): yield n yield node.left else: return yield subpaths = list() if lca.left: for node in __walk_left(lca.left, x_edge): if node == x_node: node = self.Node(Edge2D(x, node.curve.get_point(1)), self.__error) subpaths.append(node) if lca.right: right_subpaths = list() for node in __walk_right(lca.right, y_edge): if node == y_node: node = self.Node(Edge2D(node.curve.get_point(0), y), self.__error) right_subpaths.append(node) subpaths += right_subpaths[::-1] return subpaths