import math class Map(object): # Create Map. Cluster points into regions. Calculate distances only to own # and neighbor regions. We can actually cluster in O(n) when we know how # high and wide the clusters are. Once we have that working we go from # there def __init__(self, n_clusters): self.CLUSTERS_X = n_clusters self.plot_n = 0 def calc_corners(self, points): x_min, x_max = float("inf"), float("-inf") y_min, y_max = float("inf"), float("-inf") for p in points: if p.x < x_min: x_min = p.x if p.x > x_max: x_max = p.x if p.y < y_min: y_min = p.y if p.y > y_max: y_max = p.y self.x_min = x_min self.x_max = x_max self.y_min = y_min self.y_max = y_max def calc_cluster_dim(self, points): # clusters = len(points) // self.CLUSTER_SIZE # Calculate number of clusters to have a square # self.clusters_x = math.ceil(math.sqrt(clusters)) # self.clusters_y = self.clusters_x self.clusters_x = self.CLUSTERS_X self.clusters_y = self.CLUSTERS_X self.clusters_total = self.clusters_x ** 2 self.cluster_x_dim = (self.x_max - self.x_min) / self.clusters_x self.cluster_y_dim = (self.y_max - self.y_min) / self.clusters_y def sort_points_into_clusters(self, points): self.clusters = [[[] for x in range(self.clusters_y)] for y in range(self.clusters_y)] for p in points: cluster_x = int((p.x - self.x_min) // self.cluster_x_dim) cluster_y = int((p.y - self.y_min) // self.cluster_y_dim) # If the point is on the outer edge of the highest cluster # the index will be outside the correct range. We put it # into the closes cluster. if cluster_x == self.clusters_x: cluster_x -= 1 if cluster_y == self.clusters_y: cluster_y -= 1 self.clusters[cluster_x][cluster_y].append(p) p.cluster_x = cluster_x p.cluster_y = cluster_y def add_neighbors_to_points(self, points): """ Add all points from the surrounding clusters to each point. """ for p in points: clusters_x = [p.cluster_x] clusters_y = [p.cluster_y] if p.cluster_x - 1 >= 0: clusters_x.append(p.cluster_x - 1) if p.cluster_x + 1 < self.clusters_x: clusters_x.append(p.cluster_x + 1) if p.cluster_y - 1 >= 0: clusters_y.append(p.cluster_y - 1) if p.cluster_y + 1 < self.clusters_y: clusters_y.append(p.cluster_y + 1) clusters = [(x, y) for x in clusters_x for y in clusters_y] neighbors = [] for x, y in clusters: for p2 in self.clusters[x][y]: if p is not p2: neighbors.append(p2) p.add_neighbors(neighbors) def cluster(self, points): """ Splits the map into clusters of a size so that each cluster contains CLUSTER_SIZE points on average. Adds all points from the current cluster and the adjacent clusters to each point. """ self.calc_corners(points) self.calc_cluster_dim(points) self.sort_points_into_clusters(points) self.add_neighbors_to_points(points) return points def plot_grid(self, plt): if plt is None: return for x_i in range(self.clusters_x + 1): x_1 = self.x_min + x_i * self.cluster_x_dim x_2 = x_1 y_1 = self.y_min y_2 = self.y_max plt.plot([x_1, x_2], [y_1, y_2], 'y:', linewidth=0.1) for y_i in range(self.clusters_y + 1): x_1 = self.x_min x_2 = self.x_max y_1 = self.y_min + y_i * self.cluster_y_dim y_2 = y_1 plt.plot([x_1, x_2], [y_1, y_2], 'y:', linewidth=0.1) def plot(self, points, plt): if plt is None: return try: import matplotlib.pyplot as plt except ModuleNotFoundError: return plt.figure(dpi=300) def plot_arrows(): for i in range(len_points): p1 = points[i - 1] p2 = points[i] plt.plot([p1.x, p2.x], [p1.y, p2.y], 'r', linewidth=0.8) # plot_arrow(p1, p2) def plot_arrow(p1, p2): x = p1.x y = p1.y dx = p2.x - x dy = p2.y - y opt = {'head_width': 0.4, 'head_length': 0.4, 'width': 0.05, 'linewidth': 0.4, 'length_includes_head': True} plt.arrow(x, y, dx, dy, **opt) def plot_points(): for i, p in enumerate(points): plt.plot(p.x, p.y, '') # plt.text(p.x, p.y, ' ' + str(p)) for nb, _ in p.neighbors: plt.plot([p.x, nb.x], [p.y, nb.y], 'b--', linewidth=0.2) len_points = len(points) plot_arrows() plot_points() try: self.plot_grid(plt) except AttributeError: pass plt.axis('off') fig_file = "plots/step_{}.png".format(self.plot_n) plt.savefig(fig_file, bbox_inches='tight') self.plot_n += 1