class KMeansLayer(BaseLayer): def __init__(self, data): self.data = data self.k = 2 def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) k_means = KMeans(n_clusters=self.k) k_means.fit(np.vstack([x,y]).T) labels = k_means.labels_ self.cmap = create_set_cmap(set(labels), 'hsv') for l in set(labels): self.painter.set_color(self.cmap[l]) self.painter.convexhull(x[labels == l], y[labels == l]) self.painter.points(x[labels == l], y[labels == l], 2) def draw(self, proj, mouse_x, mouse_y, ui_manager): ui_manager.info('Use left and right to increase/decrease the number of clusters. k = %d' % self.k) self.painter.batch_draw() def on_key_release(self, key, modifiers): if key == pyglet.window.key.LEFT: self.k = max(2,self.k - 1) return True elif key == pyglet.window.key.RIGHT: self.k = self.k + 1 return True return False
class TrailsLayer(BaseLayer): def __init__(self): self.data = read_csv('SLOCs.csv') self.cmap = colorbrewer(self.data['name'], alpha=220) self.t = self.data['timestamp'].min() self.painter = BatchPainter() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() df = self.data.where((self.data['timestamp'] > self.t) & (self.data['timestamp'] <= self.t + 24*3600)) #set minimum time interval for name in set(df['name']): grp = df.where(df['name'] == name) self.painter.set_color('blue') #set color, default: self.cmap[name] x0, y0 = proj.lonlat_to_screen(grp['Slon'], grp['Slat']) x1, y1 = proj.lonlat_to_screen(grp['Flon'], grp['Flat']) self.painter.points(x0, y0, 3) self.painter.lines(x0,y0,x1,y1,width=3) self.t += 24*3600 #set animation step size if self.t > self.data['timestamp'].max(): self.t = self.data['timestamp'].min() self.painter.batch_draw() ui_manager.info(epoch_to_str(self.t)) def bbox(self): return BoundingBox(north=9, west=110, south=1, east=95) #set boundingbox
class KMeansLayer(BaseLayer): def __init__(self, data): self.data = data def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) k_means = KMeans() k_means.fit(np.vstack([x,y]).T) labels = k_means.labels_ self.cmap = create_set_cmap(set(labels), 'hsv') for l in set(labels): try: self.painter.set_color(self.cmap[l]) self.painter.convexhull(x[labels == l], y[labels == l]) self.painter.points(x[labels == l], y[labels == l], 2) except Exception: print '=============',l,'==============' def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def on_key_release(self, key, modifiers): return False
class TrailsLayer(BaseLayer): def __init__(self): self.data = read_csv('alex.csv') self.cmap = colorbrewer(self.data['runner_id'], alpha=220) self.t = self.data['timestamp'].min() self.painter = BatchPainter() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() df = self.data.where((self.data['timestamp'] > self.t) & (self.data['timestamp'] <= self.t + 15 * 60)) for taxi_id in set(df['runner_id']): grp = df.where(df['runner_id'] == taxi_id) self.painter.set_color(self.cmap[taxi_id]) x, y = proj.lonlat_to_screen(grp['lon'], grp['lat']) self.painter.points(x, y, 10) self.t += 2 * 60 if self.t > self.data['timestamp'].max(): self.t = self.data['timestamp'].min() self.painter.batch_draw() ui_manager.info(epoch_to_str(self.t)) # this should get modified as well moving forward def bbox(self): return BoundingBox(north=37.801421, west=-122.517339, south=37.730097, east=-122.424474)
class ConvexHullLayer(BaseLayer): def __init__(self, data, col, fill=True, point_size=4): """ Convex hull for a set of points :param data: points :param col: color :param fill: whether to fill the convexhull polygon or not :param point_size: size of the points on the convexhull. Points are not rendered if None """ self.data = data self.col = col self.fill = fill self.point_size=point_size def invalidate(self, proj): self.painter = BatchPainter() self.painter.set_color(self.col) x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) if len(x) >= 3: self.painter.convexhull(x, y, self.fill) else: self.painter.linestrip(x, y) if self.point_size > 0: self.painter.points(x, y, self.point_size) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class KMeansLayer(BaseLayer): def __init__(self, data): self.data = data self.k = 2 def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) k_means = KMeans(n_clusters=self.k) k_means.fit(np.vstack([x, y]).T) labels = k_means.labels_ self.cmap = create_set_cmap(set(labels), 'hsv') for l in set(labels): self.painter.set_color(self.cmap[l]) self.painter.convexhull(x[labels == l], y[labels == l]) self.painter.points(x[labels == l], y[labels == l], 2) def draw(self, proj, mouse_x, mouse_y, ui_manager): ui_manager.info( 'Use left and right to increase/decrease the number of clusters. k = %d' % self.k) self.painter.batch_draw() def on_key_release(self, key, modifiers): if key == pyglet.window.key.LEFT: self.k = max(2, self.k - 1) return True elif key == pyglet.window.key.RIGHT: self.k = self.k + 1 return True return False
class TrailsLayer(BaseLayer): def __init__(self): self.data = read_csv('data/taxi.csv') self.cmap = colorbrewer(self.data['taxi_id'], alpha=220) self.t = self.data['timestamp'].min() self.painter = BatchPainter() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() df = self.data.where((self.data['timestamp'] > self.t) & (self.data['timestamp'] <= self.t + 15*60)) for taxi_id in set(df['taxi_id']): grp = df.where(df['taxi_id'] == taxi_id) self.painter.set_color(self.cmap[taxi_id]) x, y = proj.lonlat_to_screen(grp['lon'], grp['lat']) self.painter.points(x, y, 10) self.t += 2*60 if self.t > self.data['timestamp'].max(): self.t = self.data['timestamp'].min() self.painter.batch_draw() ui_manager.info(epoch_to_str(self.t)) def bbox(self): return BoundingBox(north=40.110222, west=115.924463, south=39.705711, east=116.803369)
class ConvexHullLayer(BaseLayer): def __init__(self, data, col, fill=True, point_size=4): """ Convex hull for a set of points :param data: points :param col: color :param fill: whether to fill the convexhull polygon or not :param point_size: size of the points on the convexhull. Points are not rendered if None """ self.data = data self.col = col self.fill = fill self.point_size = point_size def invalidate(self, proj): self.painter = BatchPainter() self.painter.set_color(self.col) x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) if len(x) >= 3: self.painter.convexhull(x, y, self.fill) else: self.painter.linestrip(x, y) if self.point_size > 0: self.painter.points(x, y, self.point_size) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class TrailsLayer(BaseLayer): def __init__(self): self.data = read_csv('data/taxi.csv') self.cmap = colorbrewer(self.data['taxi_id'], alpha=220) self.t = self.data['timestamp'].min() self.painter = BatchPainter() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() df = self.data.where((self.data['timestamp'] > self.t) & (self.data['timestamp'] <= self.t + 15 * 60)) for taxi_id in set(df['taxi_id']): grp = df.where(df['taxi_id'] == taxi_id) self.painter.set_color(self.cmap[taxi_id]) x, y = proj.lonlat_to_screen(grp['lon'], grp['lat']) self.painter.points(x, y, 10) self.t += 2 * 60 if self.t > self.data['timestamp'].max(): self.t = self.data['timestamp'].min() self.painter.batch_draw() ui_manager.info(epoch_to_str(self.t)) def bbox(self): return BoundingBox(north=40.110222, west=115.924463, south=39.705711, east=116.803369)
class GraphLayer(BaseLayer): def __init__(self, data, src_lat, src_lon, dest_lat, dest_lon, linewidth=1, alpha=220, color='hot'): """Create a graph drawing a line between each pair of (src_lat, src_lon) and (dest_lat, dest_lon) :param data: data access object :param src_lat: field name of source latitude :param src_lon: field name of source longitude :param dest_lat: field name of destination latitude :param dest_lon: field name of destination longitude :param linewidth: line width :param alpha: color alpha :param color: color or colormap """ self.data = data self.src_lon = src_lon self.src_lat = src_lat self.dest_lon = dest_lon self.dest_lat = dest_lat self.linewidth = linewidth alpha = alpha self.color = color if type(self.color) == str: self.cmap = colors.ColorMap(self.color, alpha) def invalidate(self, proj): self.painter = BatchPainter() x0, y0 = proj.lonlat_to_screen(self.data[self.src_lon], self.data[self.src_lat]) x1, y1 = proj.lonlat_to_screen(self.data[self.dest_lon], self.data[self.dest_lat]) if type(self.color) == list: self.painter.set_color(self.color) self.painter.lines(x0, y0, x1, y1, width=self.linewidth) else: manhattan = np.abs(x0-x1) + np.abs(y0-y1) vmax = manhattan.max() distances = np.logspace(0, log10(manhattan.max()), 20) for i in range(len(distances)-1, 1, -1): mask = (manhattan > distances[i-1]) & (manhattan <= distances[i]) self.painter.set_color(self.cmap.to_color(distances[i], vmax, 'log')) self.painter.lines(x0[mask], y0[mask], x1[mask], y1[mask], width=self.linewidth) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points(lons=np.hstack([self.data[self.src_lon], self.data[self.dest_lon]]), lats=np.hstack([self.data[self.src_lat], self.data[self.dest_lat]]))
class PointsLayer(BaseLayer): def __init__(self, data, color, point_size): self.data = data self.color = color self.point_size = point_size def invalidate(self, proj): x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) self.painter = BatchPainter() self.painter.set_color(self.color) self.painter.points(x, y, point_size=self.point_size, rounded=True) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class DotDensityLayer(BaseLayer): def __init__(self, data, color=None, point_size=2, f_tooltip=None): """Create a dot density map :param data: data access object :param color: color :param point_size: point size :param f_tooltip: function to return a tooltip string for a point """ self.frame_counter = 0 self.data = data self.color = color if self.color is None: self.color = [255,0,0] self.point_size = point_size self.f_tooltip = f_tooltip self.hotspots = HotspotManager() def invalidate(self, proj): self.x, self.y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) if self.f_tooltip: for i in range(0, len(x)): record = {k: self.data[k][i] for k in self.data.keys()} self.hotspots.add_rect(x[i] - self.point_size, y[i] - self.point_size, 2*self.point_size, 2*self.point_size, self.f_tooltip(record)) self.color= "blue" #random.choice(foo) self.painter.set_color(self.color) self.painter.points(x[self.frame_counter], y[self.frame_counter], 2*self.point_size, False) self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) self.frame_counter += 1 time.sleep(0.4) if self.frame_counter == len(x): self.frame_counter = 0 def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class MarkersLayer(BaseLayer): def __init__(self, data, marker, f_tooltip=None, marker_preferred_size=32): """ Draw markers :param data: data access object :param marker: full filename of the marker image :param f_tooltip: function to generate a tooltip on mouseover :param marker_preferred_size: size in pixel for the marker images """ self.data = data self.f_tooltip = f_tooltip self.marker_preferred_size = float(marker_preferred_size) self.marker = pyglet.image.load(marker) self.marker.anchor_x = self.marker.width / 2 self.marker.anchor_y = self.marker.height / 2 self.scale = self.marker_preferred_size / max(self.marker.width, self.marker.height) self.hotspots = HotspotManager() def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) if self.f_tooltip: for i in range(0, len(x)): record = {k: self.data[k][i] for k in self.data.keys()} self.hotspots.add_rect(x[i] - self.marker_preferred_size/2, y[i] - self.marker_preferred_size/2, self.marker_preferred_size, self.marker_preferred_size, self.f_tooltip(record)) self.painter.sprites(self.marker, x, y, self.scale) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class LabelsLayer(BaseLayer): def __init__(self, data, label_column, color=None, font_name=FONT_NAME, font_size=14, anchor_x='left', anchor_y='top'): """Create a layer with a text label for each sample :param data: data access object :param label_column: column in the data access object where the labels text is stored :param color: color :param font_name: font name :param font_size: font size :param anchor_x: anchor x :param anchor_y: anchor y """ self.data = data self.label_column = label_column self.color = color self.font_name = font_name self.font_size = font_size self.anchor_x = anchor_x self.anchor_y = anchor_y if self.color is None: self.color = [255, 0, 0] def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) self.painter.set_color(self.color) self.painter.labels(x, y, self.data[self.label_column], font_name=self.font_name, font_size=self.font_size, anchor_x=self.anchor_x, anchor_y=self.anchor_y) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class QuadsLayer(BaseLayer): def __init__(self, data, cmap='hot_r'): self.data = data if cmap is not None: self.cmap = geoplotlib.colors.ColorMap(cmap, alpha=196) else: self.cmap = None def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) w = x.max() - x.min() h = y.max() - y.min() w = np.ceil(w / 2) * 2 h = np.ceil(h / 2) * 2 l = max(w, h) root = QuadTree(x.min(), x.min() + l, y.min() + l, y.min()) maxarea = (root.right - root.left) * (root.top - root.bottom) queue = [root] done = [] while len(queue) > 0: qt = queue.pop() if qt.can_split(x, y): queue.extend(qt.split()) else: done.append(qt) print len(queue), len(done) if self.cmap is not None: for qt in done: area = (qt.right - qt.left) * (qt.top - qt.bottom) self.painter.set_color(self.cmap.to_color(1 + area, 1 + maxarea, 'log')) self.painter.rect(qt.left, qt.top, qt.right, qt.bottom) else: for qt in done: self.painter.linestrip([qt.left, qt.right, qt.right, qt.left], [qt.top, qt.top, qt.bottom, qt.bottom], closed=True) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class QuadsLayer(BaseLayer): def __init__(self, data, cmap='hot_r'): self.data = data if cmap is not None: self.cmap = geoplotlib.colors.ColorMap(cmap, alpha=196) else: self.cmap = None def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) w = x.max() - x.min() h = y.max() - y.min() w = np.ceil(w / 2) * 2 h = np.ceil(h / 2) * 2 l = max(w, h) root = QuadTree(x.min(), x.min() + l, y.min() + l, y.min()) maxarea = (root.right - root.left) * (root.top - root.bottom) queue = [root] done = [] while len(queue) > 0: qt = queue.pop() if qt.can_split(x, y): queue.extend(qt.split()) else: done.append(qt) print((len(queue), len(done))) if self.cmap is not None: for qt in done: area = (qt.right - qt.left) * (qt.top - qt.bottom) self.painter.set_color( self.cmap.to_color(1 + area, 1 + maxarea, 'log')) self.painter.rect(qt.left, qt.top, qt.right, qt.bottom) else: for qt in done: self.painter.linestrip([qt.left, qt.right, qt.right, qt.left], [qt.top, qt.top, qt.bottom, qt.bottom], closed=True) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class AnimatedLayer(BaseLayer): """ geoplotlib 动画类图层 """ def __init__(self, data): self.data = data self.frame_counter = 0 def invalidate(self, proj): self.x, self.y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() self.painter.points(self.x[self.frame_counter], self.y[self.frame_counter]) self.painter.batch_draw() self.frame_counter += 1
class DotDensityLayer(BaseLayer): def __init__(self, data, color=None, point_size=2, f_tooltip=None): """Create a dot density map :param data: data access object :param color: color :param point_size: point size :param f_tooltip: function to return a tooltip string for a point """ self.data = data self.color = color if self.color is None: self.color = [255,0,0] self.point_size = point_size self.f_tooltip = f_tooltip self.hotspots = HotspotManager() def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) if self.f_tooltip: for i in range(0, len(x)): record = {k: self.data[k][i] for k in self.data.keys()} self.hotspots.add_rect(x[i] - self.point_size, y[i] - self.point_size, 2*self.point_size, 2*self.point_size, self.f_tooltip(record)) self.painter.set_color(self.color) self.painter.points(x, y, 2*self.point_size, False) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class LabelsLayer(BaseLayer): def __init__(self, data, label_column, color=None, font_name=FONT_NAME, font_size=14, anchor_x='left', anchor_y='top'): """Create a layer with a text label for each sample :param data: data access object :param label_column: column in the data access object where the labels text is stored :param color: color :param font_name: font name :param font_size: font size :param anchor_x: anchor x :param anchor_y: anchor y """ self.data = data self.label_column = label_column self.color = color self.font_name = font_name self.font_size = font_size self.anchor_x = anchor_x self.anchor_y = anchor_y if self.color is None: self.color = [255,0,0] def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) self.painter.set_color(self.color) self.painter.labels(x, y, self.data[self.label_column], font_name=self.font_name, font_size=self.font_size, anchor_x=self.anchor_x, anchor_y=self.anchor_y) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class GridLayer(BaseLayer): def __init__(self, lon_edges, lat_edges, values, cmap, alpha=255, vmin=None, vmax=None): self.lon_edges = lon_edges self.lat_edges = lat_edges self.values = values self.cmap = colors.ColorMap(cmap, alpha=alpha) if vmin: self.vmin = vmin else: self.vmin = 0 if vmax: self.vmax = vmax else: self.vmax = self.values.max() def invalidate(self, proj): self.painter = BatchPainter() xv, yv = proj.lonlat_to_screen(self.lon_edges, self.lat_edges) rects = [] cols = [] for ix in range(len(xv) - 1): for iy in range(len(yv) - 1): d = self.values[iy, ix] if d > self.vmin: rects.append((xv[ix], yv[iy], xv[ix + 1], yv[iy + 1])) cols.append(self.cmap.to_color(d, self.vmax, 'lin')) self.painter.batch_rects(rects, cols) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class TrailsLayer(BaseLayer): def __init__(self): self.data = read_csv('data/taxi.csv') self.data = self.data.where(self.data['taxi_id'] == list(set(self.data['taxi_id']))[2]) self.t = self.data['timestamp'].min() self.painter = BatchPainter() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter = BatchPainter() self.painter.set_color([0,0,255]) df = self.data.where((self.data['timestamp'] > self.t) & (self.data['timestamp'] <= self.t + 30*60)) proj.fit(BoundingBox.from_points(lons=df['lon'], lats=df['lat']), max_zoom=14) x, y = proj.lonlat_to_screen(df['lon'], df['lat']) self.painter.linestrip(x, y, 10) self.t += 30 if self.t > self.data['timestamp'].max(): self.t = self.data['timestamp'].min() self.painter.batch_draw() ui_manager.info(epoch_to_str(self.t))
class GridLayer(BaseLayer): def __init__(self, lon_edges, lat_edges, values, cmap, alpha=255, vmin=None, vmax=None): self.lon_edges = lon_edges self.lat_edges = lat_edges self.values = values self.cmap = colors.ColorMap(cmap, alpha=alpha) if vmin: self.vmin = vmin else: self.vmin = 0 if vmax: self.vmax = vmax else: self.vmax = self.values.max() def invalidate(self, proj): self.painter = BatchPainter() xv, yv = proj.lonlat_to_screen(self.lon_edges, self.lat_edges) rects = [] cols = [] for ix in range(len(xv)-1): for iy in range(len(yv)-1): d = self.values[iy, ix] if d > self.vmin: rects.append((xv[ix], yv[iy], xv[ix+1], yv[iy+1])) cols.append(self.cmap.to_color(d, self.vmax, 'lin')) self.painter.batch_rects(rects, cols) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()
class VoronoiLayer(BaseLayer): def __init__(self, data, line_color=None, line_width=2, f_tooltip=None, cmap=None, max_area=1e4, alpha=220): """ Draw the voronoi tesselation of the points from data :param data: data access object :param line_color: line color :param line_width: line width :param f_tooltip: function to generate a tooltip on mouseover :param cmap: color map :param max_area: scaling constant to determine the color of the voronoi areas :param alpha: color alpha :return: """ self.data = data if cmap is None and line_color is None: raise Exception('need either cmap or line_color') if cmap is not None: cmap = colors.ColorMap(cmap, alpha=alpha, levels=10) self.cmap = cmap self.line_color = line_color self.line_width = line_width self.f_tooltip = f_tooltip self.max_area = max_area # source: https://gist.github.com/pv/8036995 @staticmethod def __voronoi_finite_polygons_2d(vor, radius=None): """ Reconstruct infinite voronoi regions in a 2D diagram to finite regions. Parameters ---------- vor : Voronoi Input diagram radius : float, optional Distance to 'points at infinity'. Returns ------- regions : list of tuples Indices of vertices in each revised Voronoi regions. vertices : list of tuples Coordinates for revised Voronoi vertices. Same as coordinates of input vertices, with 'points at infinity' appended to the end. """ if vor.points.shape[1] != 2: raise ValueError("Requires 2D input") new_regions = [] new_vertices = vor.vertices.tolist() center = vor.points.mean(axis=0) if radius is None: radius = vor.points.ptp().max() # Construct a map containing all ridges for a given point all_ridges = {} for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices): all_ridges.setdefault(p1, []).append((p2, v1, v2)) all_ridges.setdefault(p2, []).append((p1, v1, v2)) # Reconstruct infinite regions for p1, region in enumerate(vor.point_region): vertices = vor.regions[region] if all(v >= 0 for v in vertices): # finite region new_regions.append(vertices) continue # reconstruct a non-finite region if p1 not in all_ridges: continue ridges = all_ridges[p1] new_region = [v for v in vertices if v >= 0] for p2, v1, v2 in ridges: if v2 < 0: v1, v2 = v2, v1 if v1 >= 0: # finite ridge: already in the region continue # Compute the missing endpoint of an infinite ridge t = vor.points[p2] - vor.points[p1] # tangent t /= np.linalg.norm(t) n = np.array([-t[1], t[0]]) # normal midpoint = vor.points[[p1, p2]].mean(axis=0) direction = np.sign(np.dot(midpoint - center, n)) * n far_point = vor.vertices[v2] + direction * radius new_region.append(len(new_vertices)) new_vertices.append(far_point.tolist()) # sort region counterclockwise vs = np.asarray([new_vertices[v] for v in new_region]) c = vs.mean(axis=0) angles = np.arctan2(vs[:, 1] - c[1], vs[:, 0] - c[0]) new_region = np.array(new_region)[np.argsort(angles)] # finish new_regions.append(new_region.tolist()) return new_regions, np.asarray(new_vertices) # Area of a polygon: http://www.mathopenref.com/coordpolygonarea.html @staticmethod def _get_area(p): return 0.5 * abs( sum(x0 * y1 - x1 * y0 for ((x0, y0), (x1, y1)) in zip(p, p[1:] + [p[0]]))) def invalidate(self, proj): try: from scipy.spatial.qhull import Voronoi except ImportError: print('VoronoiLayer needs scipy >= 0.12') raise x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) points = list(zip(x, y)) vor = Voronoi(points) regions, vertices = VoronoiLayer.__voronoi_finite_polygons_2d(vor) self.hotspots = HotspotManager() self.painter = BatchPainter() for idx, region in enumerate(regions): polygon = vertices[region] if self.line_color: self.painter.set_color(self.line_color) self.painter.linestrip(polygon[:, 0], polygon[:, 1], width=self.line_width, closed=True) if self.cmap: area = VoronoiLayer._get_area(polygon.tolist()) area = max(area, 1) self.painter.set_color( self.cmap.to_color(area, self.max_area, 'log')) self.painter.poly(polygon[:, 0], polygon[:, 1]) if self.f_tooltip: record = {k: self.data[k][idx] for k in list(self.data.keys())} self.hotspots.add_poly(polygon[:, 0], polygon[:, 1], self.f_tooltip(record)) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class DelaunayLayer(BaseLayer): def __init__(self, data, line_color=None, line_width=2, cmap=None, max_lenght=100): """ Draw a delaunay triangulation of the points :param data: data access object :param line_color: line color :param line_width: line width :param cmap: color map :param max_lenght: scaling constant for coloring the edges """ self.data = data if cmap is None and line_color is None: raise Exception('need either cmap or line_color') if cmap is not None: cmap = colors.ColorMap(cmap, alpha=196) self.cmap = cmap self.line_color = line_color self.line_width = line_width self.max_lenght = max_lenght @staticmethod def _get_area(p): x1, y1, x2, y2, x3, y3 = p return 0.5 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) def invalidate(self, proj): try: from scipy.spatial.qhull import Delaunay except ImportError: print('DelaunayLayer needs scipy >= 0.12') raise self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) points = list(set(zip(x, y))) dela = Delaunay(points) edges = set() for tria in dela.vertices: edges.add((tria[0], tria[1])) edges.add((tria[1], tria[2])) edges.add((tria[2], tria[0])) allx0 = [] ally0 = [] allx1 = [] ally1 = [] colors = [] for a, b in edges: x0, y0 = dela.points[a] x1, y1 = dela.points[b] allx0.append(x0) ally0.append(y0) allx1.append(x1) ally1.append(y1) if self.line_color: colors.append(self.line_color) colors.append(self.line_color) elif self.cmap: l = math.sqrt((x0 - x1)**2 + (y0 - y1)**2) c = self.cmap.to_color(l, self.max_lenght, 'log') colors.append(c) colors.append(c) self.painter.lines(allx0, ally0, allx1, ally1, colors, width=self.line_width) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class ShapefileLayer(BaseLayer): def __init__(self, fname, f_tooltip=None, color=None, linewidth=3, shape_type='full'): """ Loads and draws shapefiles :param fname: full path to the shapefile :param f_tooltip: function to generate a tooltip on mouseover :param color: color :param linewidth: line width :param shape_type: either full or bbox """ if color is None: color = [255, 0, 0] self.color = color self.linewidth = linewidth self.f_tooltip = f_tooltip self.shape_type = shape_type try: import shapefile except: raise Exception('ShapefileLayer requires pyshp') self.reader = shapefile.Reader(fname) self.worker = None self.queue = queue.Queue() def invalidate(self, proj): self.painter = BatchPainter() self.hotspots = HotspotManager() self.painter.set_color(self.color) if self.worker: self.worker.stop() self.worker.join() self.queue = queue.Queue() self.worker = ShapeLoadingThread(self.queue, self.reader, self.shape_type, proj) self.worker.start() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) while True: try: x, y, record = self.queue.get_nowait() self.painter.linestrip(x, y, self.linewidth, closed=True) if self.f_tooltip: attr = { t[0][0]: t[1] for t in zip(self.reader.fields[1:], record) } value = self.f_tooltip(attr) if self.shape_type == 'bbox': self.hotspots.add_rect(x.min(), y.min(), x.max() - x.min(), y.max() - y.min(), value) else: self.hotspots.add_poly(x, y, value) except queue.Empty: break
class GraphLayer(BaseLayer): def __init__(self, data, src_lat, src_lon, dest_lat, dest_lon, linewidth=1, alpha=220, color='hot'): """Create a graph drawing a line between each pair of (src_lat, src_lon) and (dest_lat, dest_lon) :param data: data access object :param src_lat: field name of source latitude :param src_lon: field name of source longitude :param dest_lat: field name of destination latitude :param dest_lon: field name of destination longitude :param linewidth: line width :param alpha: color alpha :param color: color or colormap """ self.data = data self.src_lon = src_lon self.src_lat = src_lat self.dest_lon = dest_lon self.dest_lat = dest_lat self.linewidth = linewidth alpha = alpha self.color = color if type(self.color) == str: self.cmap = colors.ColorMap(self.color, alpha) def invalidate(self, proj): self.painter = BatchPainter() x0, y0 = proj.lonlat_to_screen(self.data[self.src_lon], self.data[self.src_lat]) x1, y1 = proj.lonlat_to_screen(self.data[self.dest_lon], self.data[self.dest_lat]) if type(self.color) == list: self.painter.set_color(self.color) self.painter.lines(x0, y0, x1, y1, width=self.linewidth) else: manhattan = np.abs(x0 - x1) + np.abs(y0 - y1) vmax = manhattan.max() distances = np.logspace(0, log10(manhattan.max()), 20) for i in range(len(distances) - 1, 1, -1): mask = (manhattan > distances[i - 1]) & (manhattan <= distances[i]) self.painter.set_color( self.cmap.to_color(distances[i], vmax, 'log')) self.painter.lines(x0[mask], y0[mask], x1[mask], y1[mask], width=self.linewidth) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points( lons=np.hstack([self.data[self.src_lon], self.data[self.dest_lon]]), lats=np.hstack([self.data[self.src_lat], self.data[self.dest_lat]]))
class GeoJSONLayer(BaseLayer): def __init__(self, geojson_or_fname, color='b', linewidth=1, fill=False, f_tooltip=None): self.color = color self.linewidth = linewidth self.fill = fill self.f_tooltip = f_tooltip if type(geojson_or_fname) == str: with open(geojson_or_fname) as fin: self.data = json.load(fin) elif type(geojson_or_fname) == dict: self.data = geojson_or_fname else: raise Exception('must provide either dict or filename') self.boundingbox = None for feature in self.data['features']: if feature['geometry']['type'] == 'Polygon': for poly in feature['geometry']['coordinates']: poly = np.array(poly) self.__update_bbox(poly[:,0], poly[:,1]) elif feature['geometry']['type'] == 'MultiPolygon': for multipoly in feature['geometry']['coordinates']: for poly in multipoly: poly = np.array(poly) self.__update_bbox(poly[:,0], poly[:,1]) elif feature['geometry']['type'] == 'Point': lon,lat = feature['geometry']['coordinates'] self.__update_bbox(np.array([lon]), np.array([lat])) elif feature['geometry']['type'] == 'LineString': line = np.array(feature['geometry']['coordinates']) self.__update_bbox(line[:,0], line[:,1]) def __update_bbox(self, lon, lat): if self.boundingbox is None: self.boundingbox = BoundingBox(north=lat.max(), south=lat.min(), west=lon.min(), east=lon.max()) else: self.boundingbox = BoundingBox( north=max(self.boundingbox.north, lat.max()), south=min(self.boundingbox.south, lat.min()), west=min(self.boundingbox.west, lon.min()), east=max(self.boundingbox.east, lon.max())) def invalidate(self, proj): self.painter = BatchPainter() self.hotspots = HotspotManager() for feature in self.data['features']: if isfunction(self.color): self.painter.set_color(self.color(feature['properties'])) else: self.painter.set_color(self.color) if feature['geometry']['type'] == 'Polygon': for poly in feature['geometry']['coordinates']: poly = np.array(poly) x, y = proj.lonlat_to_screen(poly[:,0], poly[:,1]) if self.fill: self.painter.poly(x, y) else: self.painter.linestrip(x, y, self.linewidth, closed=True) if self.f_tooltip: self.hotspots.add_poly(x, y, self.f_tooltip(feature['properties'])) elif feature['geometry']['type'] == 'MultiPolygon': for multipoly in feature['geometry']['coordinates']: for poly in multipoly: poly = np.array(poly) x, y = proj.lonlat_to_screen(poly[:,0], poly[:,1]) if self.fill: self.painter.poly(x, y) else: self.painter.linestrip(x, y, self.linewidth, closed=True) if self.f_tooltip: self.hotspots.add_poly(x, y, self.f_tooltip(feature['properties'])) elif feature['geometry']['type'] == 'Point': lon,lat = feature['geometry']['coordinates'] x, y = proj.lonlat_to_screen(np.array([lon]), np.array([lat])) self.painter.points(x, y) elif feature['geometry']['type'] == 'LineString': line = np.array(feature['geometry']['coordinates']) x, y = proj.lonlat_to_screen(line[:,0], line[:,1]) self.painter.linestrip(x, y, self.linewidth, closed=False) else: print('unknow geometry %s' % feature['geometry']['type']) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): if self.boundingbox: return self.boundingbox else: return BoundingBox.WORLD
class GridLayer(BaseLayer): def __init__(self, lon_edges, lat_edges, values, cmap, alpha=255, vmin=None, vmax=None, levels=10, colormap_scale='lin', show_colorbar=True): """ Values over a uniform grid :param lon_edges: longitude edges :param lat_edges: latitude edges :param values: matrix representing values on the grid :param cmap: colormap name :param alpha: color alpha :param vmin: minimum value for the colormap :param vmax: maximum value for the colormap :param levels: number of levels for the colormap :param colormap_scale: colormap scale :param show_colorbar: show the colorbar in the UI """ self.lon_edges = lon_edges self.lat_edges = lat_edges self.values = values self.cmap = colors.ColorMap(cmap, alpha=alpha, levels=levels) self.colormap_scale = colormap_scale self.show_colorbar = show_colorbar if vmin: self.vmin = vmin else: self.vmin = 0 if vmax: self.vmax = vmax else: self.vmax = self.values[~np.isnan(self.values)].max() def invalidate(self, proj): self.painter = BatchPainter() xv, yv = proj.lonlat_to_screen(self.lon_edges, self.lat_edges) rects = [] cols = [] for ix in range(len(xv) - 1): for iy in range(len(yv) - 1): d = self.values[iy, ix] if d > self.vmin: rects.append((xv[ix], yv[iy], xv[ix + 1], yv[iy + 1])) cols.append( self.cmap.to_color(d, self.vmax, self.colormap_scale)) self.painter.batch_rects(rects, cols) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() if self.show_colorbar: ui_manager.add_colorbar(self.cmap, self.vmax, self.colormap_scale) def bbox(self): return BoundingBox(north=self.lat_edges[-1], south=self.lat_edges[0], west=self.lon_edges[0], east=self.lon_edges[-1])
class LineLayer(layers.BaseLayer): """ Draws line connecting points in hurricane path """ def __init__(self, data, num, color=None, point_size=2, linewidth=1, f_tooltip=None): """ Creates a LineLayer for the python geoplotlib library :param data: DataAccessObject of latitudes and longitudes :param num: number of data points in dat :param color: color of lines, just to red if none :param point_size: size of points :param linewidth: width of lines :param f_tooltip: an attribute of geoplotlib's BaseLayer that is not used """ self.data = data self.indexlst = num self.color = color if self.color is None: self.color = [255, 0, 0] self.point_size = point_size self.f_tooltip = f_tooltip self.linewidth = linewidth self.hotspots = HotspotManager() def invalidate(self, proj): """ This method is called each time layers need to be redrawn, i.e. on zoom. Typically in this method a BatchPainter is instantiated and all the rendering is performed :param proj: the current Projector object """ self.painter = BatchPainter() self.painter.set_color(self.color) x1, y1 = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) self.painter.points(x1, y1, 2 * self.point_size, False) for i in self.indexlst: if i < len(self.data['lon']) - 1: x1 = self.data['lon'][i] y1 = self.data['lat'][i] x2 = self.data['lon'][i+1] y2 = self.data['lat'][i+1] x1, y1 = proj.lonlat_to_screen(x1, y1) x2, y2 = proj.lonlat_to_screen(x2, y2) self.painter.lines(x1, y1, x2, y2, width=self.linewidth) if self.f_tooltip: for i in range(0, len(x1)): record = {k: self.data[k][i] for k in self.data.keys()} self.hotspots.add_rect(x1[i] - self.point_size, y1[i] - self.point_size, 2 * self.point_size, 2 * self.point_size, self.f_tooltip(record)) def draw(self, proj, mouse_x, mouse_y, ui_manager): """ This method is called at every frame, and typically executes BatchPainter.batch_draw() :param proj: the current Projector object :param mouse_x: mouse x :param mouse_y: mouse y :param ui_manager: the current UiManager """ self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): """ Return the bounding box for this layer """ return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat']) def on_key_release(self, key, modifiers): """ Override this method for custom handling of keystrokes :param key: the key that has been released :param modifiers: the key modifiers :return: True if the layer needs to call invalidate """ return False
class DelaunayLayer(BaseLayer): def __init__(self, data, line_color=None, line_width=2, cmap=None, max_lenght=100): """ Draw a delaunay triangulation of the points :param data: data access object :param line_color: line color :param line_width: line width :param cmap: color map :param max_lenght: scaling constant for coloring the edges """ self.data = data if cmap is None and line_color is None: raise Exception('need either cmap or line_color') if cmap is not None: cmap = colors.ColorMap(cmap, alpha=196) self.cmap = cmap self.line_color = line_color self.line_width = line_width self.max_lenght = max_lenght @staticmethod def _get_area(p): x1, y1, x2, y2, x3, y3 = p return 0.5*(x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2)) def invalidate(self, proj): try: from scipy.spatial.qhull import Delaunay except ImportError: print('DelaunayLayer needs scipy >= 0.12') raise self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) points = list(set(zip(x,y))) dela = Delaunay(points) edges = set() for tria in dela.vertices: edges.add((tria[0], tria[1])) edges.add((tria[1], tria[2])) edges.add((tria[2], tria[0])) allx0 = [] ally0 = [] allx1 = [] ally1 = [] colors = [] for a, b in edges: x0, y0 = dela.points[a] x1, y1 = dela.points[b] allx0.append(x0) ally0.append(y0) allx1.append(x1) ally1.append(y1) if self.line_color: colors.append(self.line_color) colors.append(self.line_color) elif self.cmap: l = math.sqrt((x0 - x1)**2+(y0 - y1)**2) c = self.cmap.to_color(l, self.max_lenght, 'log') colors.append(c) colors.append(c) self.painter.lines(allx0, ally0, allx1, ally1, colors, width=self.line_width) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class GraphLayer(BaseLayer): def __init__(self, data, src_lat, src_lon, dest_lat, dest_lon, linewidth=1, alpha=220, color='hot', levels=10, color_by=None, seg_scale='log'): """Create a graph drawing a line between each pair of (src_lat, src_lon) and (dest_lat, dest_lon) :param data: data access object :param src_lat: field name of source latitude :param src_lon: field name of source longitude :param dest_lat: field name of destination latitude :param dest_lon: field name of destination longitude :param linewidth: line width :param alpha: color alpha :param color: color or colormap :param levels: coloring levels :param color_by: attribute name for color, default using node distance :param seg_scale: coloring data segamentation sacle, 'log' or 'lin', 'lin' only used if not by distance """ self.data = data self.src_lon = src_lon self.src_lat = src_lat self.dest_lon = dest_lon self.dest_lat = dest_lat self.linewidth = linewidth alpha = alpha self.color = color if type(self.color) == str: self.cmap = colors.ColorMap(self.color, alpha, levels=levels) if color_by is None: self.color_by = 'distance' self.seg_scale = seg_scale def invalidate(self, proj): self.painter = BatchPainter() x0, y0 = proj.lonlat_to_screen(self.data[self.src_lon], self.data[self.src_lat]) x1, y1 = proj.lonlat_to_screen(self.data[self.dest_lon], self.data[self.dest_lat]) if type(self.color) == list: self.painter.set_color(self.color) self.painter.lines(x0, y0, x1, y1, width=self.linewidth) else: if self.color_by == 'distance': manhattan = np.abs(x0 - x1) + np.abs(y0 - y1) vmax = manhattan.max() segmentations = np.logspace(0, log10(vmax), 20) self.seg_scale = 'log' else: manhattan = self.data[self.color_by] vmax = manhattan.max() if self.seg_scale == 'log': # value 20 maybe should be optional segmentations = np.logspace(0, log10(vmax), 20) else: # linear segmentations = np.linspace(0, vmax, 20) for i in range(len(segmentations) - 1, 1, -1): mask = (manhattan > segmentations[i - 1]) & (manhattan <= segmentations[i]) self.painter.set_color( self.cmap.to_color(segmentations[i], vmax, self.seg_scale)) self.painter.lines(x0[mask], y0[mask], x1[mask], y1[mask], width=self.linewidth) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() def bbox(self): return BoundingBox.from_points( lons=np.hstack([self.data[self.src_lon], self.data[self.dest_lon]]), lats=np.hstack([self.data[self.src_lat], self.data[self.dest_lat]]))
class GridLayer(BaseLayer): def __init__(self, lon_edges, lat_edges, values, cmap, alpha=255, vmin=None, vmax=None, levels=10, colormap_scale='lin', show_colorbar=True): """ Values over a uniform grid :param lon_edges: longitude edges :param lat_edges: latitude edges :param values: matrix representing values on the grid :param cmap: colormap name :param alpha: color alpha :param vmin: minimum value for the colormap :param vmax: maximum value for the colormap :param levels: number of levels for the colormap :param colormap_scale: colormap scale :param show_colorbar: show the colorbar in the UI """ self.lon_edges = lon_edges self.lat_edges = lat_edges self.values = values self.cmap = colors.ColorMap(cmap, alpha=alpha, levels=levels) self.colormap_scale = colormap_scale self.show_colorbar = show_colorbar if vmin: self.vmin = vmin else: self.vmin = 0 if vmax: self.vmax = vmax else: self.vmax = self.values[~np.isnan(self.values)].max() def invalidate(self, proj): self.painter = BatchPainter() xv, yv = proj.lonlat_to_screen(self.lon_edges, self.lat_edges) rects = [] cols = [] for ix in range(len(xv)-1): for iy in range(len(yv)-1): d = self.values[iy, ix] if d > self.vmin: rects.append((xv[ix], yv[iy], xv[ix+1], yv[iy+1])) cols.append(self.cmap.to_color(d, self.vmax, self.colormap_scale)) self.painter.batch_rects(rects, cols) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() if self.show_colorbar: ui_manager.add_colorbar(self.cmap, self.vmax, self.colormap_scale) def bbox(self): return BoundingBox(north=self.lat_edges[-1], south=self.lat_edges[0], west=self.lon_edges[0], east=self.lon_edges[-1])
class VoronoiLayer(BaseLayer): def __init__(self, data, line_color=None, line_width=2, f_tooltip=None, cmap=None, max_area=1e4, alpha=220): """ Draw the voronoi tesselation of the points from data :param data: data access object :param line_color: line color :param line_width: line width :param f_tooltip: function to generate a tooltip on mouseover :param cmap: color map :param max_area: scaling constant to determine the color of the voronoi areas :param alpha: color alpha :return: """ self.data = data if cmap is None and line_color is None: raise Exception('need either cmap or line_color') if cmap is not None: cmap = colors.ColorMap(cmap, alpha=alpha, levels=10) self.cmap = cmap self.line_color = line_color self.line_width = line_width self.f_tooltip = f_tooltip self.max_area = max_area # source: https://gist.github.com/pv/8036995 @staticmethod def __voronoi_finite_polygons_2d(vor, radius=None): """ Reconstruct infinite voronoi regions in a 2D diagram to finite regions. Parameters ---------- vor : Voronoi Input diagram radius : float, optional Distance to 'points at infinity'. Returns ------- regions : list of tuples Indices of vertices in each revised Voronoi regions. vertices : list of tuples Coordinates for revised Voronoi vertices. Same as coordinates of input vertices, with 'points at infinity' appended to the end. """ if vor.points.shape[1] != 2: raise ValueError("Requires 2D input") new_regions = [] new_vertices = vor.vertices.tolist() center = vor.points.mean(axis=0) if radius is None: radius = vor.points.ptp().max() # Construct a map containing all ridges for a given point all_ridges = {} for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices): all_ridges.setdefault(p1, []).append((p2, v1, v2)) all_ridges.setdefault(p2, []).append((p1, v1, v2)) # Reconstruct infinite regions for p1, region in enumerate(vor.point_region): vertices = vor.regions[region] if all(v >= 0 for v in vertices): # finite region new_regions.append(vertices) continue # reconstruct a non-finite region if p1 not in all_ridges: continue ridges = all_ridges[p1] new_region = [v for v in vertices if v >= 0] for p2, v1, v2 in ridges: if v2 < 0: v1, v2 = v2, v1 if v1 >= 0: # finite ridge: already in the region continue # Compute the missing endpoint of an infinite ridge t = vor.points[p2] - vor.points[p1] # tangent t /= np.linalg.norm(t) n = np.array([-t[1], t[0]]) # normal midpoint = vor.points[[p1, p2]].mean(axis=0) direction = np.sign(np.dot(midpoint - center, n)) * n far_point = vor.vertices[v2] + direction * radius new_region.append(len(new_vertices)) new_vertices.append(far_point.tolist()) # sort region counterclockwise vs = np.asarray([new_vertices[v] for v in new_region]) c = vs.mean(axis=0) angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0]) new_region = np.array(new_region)[np.argsort(angles)] # finish new_regions.append(new_region.tolist()) return new_regions, np.asarray(new_vertices) # Area of a polygon: http://www.mathopenref.com/coordpolygonarea.html @staticmethod def _get_area(p): return 0.5 * abs(sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in zip(p, p[1:] + [p[0]]))) def invalidate(self, proj): try: from scipy.spatial.qhull import Voronoi except ImportError: print('VoronoiLayer needs scipy >= 0.12') raise x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) points = list(set(zip(x,y))) vor = Voronoi(points) regions, vertices = VoronoiLayer.__voronoi_finite_polygons_2d(vor) self.hotspots = HotspotManager() self.painter = BatchPainter() for idx, region in enumerate(regions): polygon = vertices[region] if self.line_color: self.painter.set_color(self.line_color) self.painter.linestrip(polygon[:,0], polygon[:,1], width=self.line_width, closed=True) if self.cmap: area = VoronoiLayer._get_area(polygon.tolist()) area = max(area, 1) self.painter.set_color(self.cmap.to_color(area, self.max_area, 'log')) self.painter.poly(polygon[:,0], polygon[:,1]) if self.f_tooltip: record = {k: self.data[k][idx] for k in self.data.keys()} self.hotspots.add_poly(polygon[:,0], polygon[:,1], self.f_tooltip(record)) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class KDELayer(BaseLayer): def __init__(self, values, bw, cmap='hot', method='hist', scaling='sqrt', alpha=220, cut_below=None, clip_above=None, binsize=1, cmap_levels=10, show_colorbar=False): """ Kernel density estimation visualization :param data: data access object :param bw: kernel bandwidth (in screen coordinates) :param cmap: colormap :param method: if kde use KDEMultivariate from statsmodel, which provides a more accurate but much slower estimation. If hist, estimates density applying gaussian smoothing on a 2D histogram, which is much faster but less accurate :param scaling: colorscale, lin log or sqrt :param alpha: color alpha :param cut_below: densities below cut_below are not drawn :param clip_above: defines the max value for the colorscale :param binsize: size of the bins for hist estimator :param cmap_levels: discretize colors into cmap_levels :param show_colorbar: show colorbar """ self.values = values self.bw = bw self.cmap = colors.ColorMap(cmap, alpha=alpha, levels=cmap_levels) self.method = method self.scaling = scaling self.cut_below = cut_below self.clip_above = clip_above self.binsize = binsize self.show_colorbar = show_colorbar def _get_grid(self, proj): west, north = proj.lonlat_to_screen([proj.bbox().west], [proj.bbox().north]) east, south = proj.lonlat_to_screen([proj.bbox().east], [proj.bbox().south]) xgrid = np.arange(west, east, self.binsize) ygrid = np.arange(south, north, self.binsize) return xgrid, ygrid def invalidate(self, proj): self.painter = BatchPainter() xv, yv = proj.lonlat_to_screen(self.values['lon'], self.values['lat']) rects_vertices = [] rects_colors = [] if self.method == 'kde': try: import statsmodels.api as sm except: raise Exception('KDE requires statsmodel') kde_res = sm.nonparametric.KDEMultivariate(data=[xv, yv], var_type='cc', bw=self.bw) xgrid, ygrid = self._get_grid(proj) xmesh, ymesh = np.meshgrid(xgrid, ygrid) grid_coords = np.append(xmesh.reshape(-1, 1), ymesh.reshape(-1, 1), axis=1) z = kde_res.pdf(grid_coords.T) z = z.reshape(len(ygrid), len(xgrid)) # np.save('z.npy', z) # z = np.load('z.npy') print(('smallest non-zero density:', z[z > 0][0])) print(('max density:', z.max())) if self.cut_below is None: zmin = z[z > 0][0] else: zmin = self.cut_below if self.clip_above is None: zmax = z.max() else: zmax = self.clip_above for ix in range(len(xgrid) - 1): for iy in range(len(ygrid) - 1): if z[iy, ix] > zmin: rects_vertices.append((xgrid[ix], ygrid[iy], xgrid[ix + 1], ygrid[iy + 1])) rects_colors.append( self.cmap.to_color(z[iy, ix], zmax, self.scaling)) elif self.method == 'hist': try: from scipy.ndimage import gaussian_filter except: raise Exception('KDE requires scipy') xgrid, ygrid = self._get_grid(proj) H, _, _ = np.histogram2d(yv, xv, bins=(ygrid, xgrid)) if H.sum() == 0: print('no data in current view') return H = gaussian_filter(H, sigma=self.bw) print(('smallest non-zero count', H[H > 0][0])) print(('max count:', H.max())) if self.cut_below is None: Hmin = H[H > 0][0] else: Hmin = self.cut_below if self.clip_above is None: self.Hmax = H.max() else: self.Hmax = self.clip_above if self.scaling == 'ranking': from statsmodels.distributions.empirical_distribution import ECDF ecdf = ECDF(H.flatten()) for ix in range(len(xgrid) - 2): for iy in range(len(ygrid) - 2): if H[iy, ix] > Hmin: rects_vertices.append((xgrid[ix], ygrid[iy], xgrid[ix + 1], ygrid[iy + 1])) if self.scaling == 'ranking': rects_colors.append( self.cmap.to_color( ecdf(H[iy, ix]) - ecdf(Hmin), 1 - ecdf(Hmin), 'lin')) else: rects_colors.append( self.cmap.to_color(H[iy, ix], self.Hmax, self.scaling)) else: raise Exception('method not supported') self.painter.batch_rects(rects_vertices, rects_colors) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() if self.show_colorbar: ui_manager.add_colorbar(self.cmap, self.Hmax, self.scaling)
class ShapefileLayer(BaseLayer): def __init__(self, fname, f_tooltip=None, color=None, linewidth=3, shape_type='full'): """ Loads and draws shapefiles :param fname: full path to the shapefile :param f_tooltip: function to generate a tooltip on mouseover :param color: color :param linewidth: line width :param shape_type: either full or bbox """ if color is None: color = [255, 0, 0] self.color = color self.linewidth = linewidth self.f_tooltip = f_tooltip self.shape_type = shape_type try: import shapefile except: raise Exception('ShapefileLayer requires pyshp') self.reader = shapefile.Reader(fname) self.worker = None self.queue = Queue.Queue() def invalidate(self, proj): self.painter = BatchPainter() self.hotspots = HotspotManager() self.painter.set_color(self.color) if self.worker: self.worker.stop() self.worker.join() self.queue = Queue.Queue() self.worker = ShapeLoadingThread(self.queue, self.reader, self.shape_type, proj) self.worker.start() def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) while True: try: x, y, record = self.queue.get_nowait() self.painter.linestrip(x, y, self.linewidth, closed=True) if self.f_tooltip: attr = {t[0][0]: t[1] for t in zip(self.reader.fields[1:], record)} value = self.f_tooltip(attr) if self.shape_type == 'bbox': self.hotspots.add_rect(x.min(), y.min(), x.max()-x.min(), y.max()-y.min(), value) else: self.hotspots.add_poly(x, y, value) except Queue.Empty: break
class HistogramLayer(BaseLayer): def __init__(self, data, cmap='hot', alpha=220, colorscale='sqrt', binsize=16, show_tooltip=False, scalemin=0, scalemax=None, f_group=None): """Create a 2D histogram :param data: data access object :param cmap: colormap name :param alpha: color alpha :param colorscale: scaling [lin, log, sqrt] :param binsize: size of the hist bins :param show_tooltip: if True, will show the value of bins on mouseover :param scalemin: min value for displaying a bin :param scalemax: max value for a bin :param f_group: function to apply to samples in the same bin. Default is to count :return: """ self.data = data self.cmap = colors.ColorMap(cmap, alpha=alpha) self.binsize = binsize self.show_tooltip = show_tooltip self.scalemin = scalemin self.scalemax = scalemax self.colorscale = colorscale self.f_group = f_group if self.f_group is None: self.f_group = lambda grp: len(grp) def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) self.data['_xbin'] = (x / self.binsize).astype(int) self.data['_ybin'] = (y / self.binsize).astype(int) uniquevalues = set([tuple(row) for row in np.vstack([self.data['_xbin'],self.data['_ybin']]).T]) results = {(v1,v2): self.f_group(self.data.where((self.data['_xbin'] == v1) & (self.data['_ybin'] == v2))) \ for v1, v2 in uniquevalues} del self.data['_xbin'] del self.data['_ybin'] self.hotspot = HotspotManager() if self.scalemax: vmax = self.scalemax else: vmax = max(results.values()) if len(results) > 0 else 0 if vmax >= 1: for (ix, iy), value in results.items(): if value > self.scalemin: self.painter.set_color(self.cmap.to_color(value, vmax, self.colorscale)) l = self.binsize rx = ix * self.binsize ry = iy * self.binsize self.painter.rect(rx, ry, rx+l, ry+l) if self.show_tooltip: self.hotspot.add_rect(rx, ry, l, l, 'Value: %d' % value) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspot.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class GeoJSONLayer(BaseLayer): def __init__(self, geojson_or_fname, color='b', linewidth=1, fill=False, f_tooltip=None): self.color = color self.linewidth = linewidth self.fill = fill self.f_tooltip = f_tooltip if type(geojson_or_fname) == str: with open(geojson_or_fname) as fin: self.data = json.load(fin) elif type(geojson_or_fname) == dict: self.data = geojson_or_fname else: raise Exception('must provide either dict or filename') self.boundingbox = None for feature in self.data['features']: if feature['geometry']['type'] == 'Polygon': for poly in feature['geometry']['coordinates']: poly = np.array(poly) self.__update_bbox(poly[:, 0], poly[:, 1]) elif feature['geometry']['type'] == 'MultiPolygon': for multipoly in feature['geometry']['coordinates']: for poly in multipoly: poly = np.array(poly) self.__update_bbox(poly[:, 0], poly[:, 1]) elif feature['geometry']['type'] == 'Point': lon, lat = feature['geometry']['coordinates'] self.__update_bbox(np.array([lon]), np.array([lat])) elif feature['geometry']['type'] == 'LineString': line = np.array(feature['geometry']['coordinates']) self.__update_bbox(line[:, 0], line[:, 1]) def __update_bbox(self, lon, lat): if self.boundingbox is None: self.boundingbox = BoundingBox(north=lat.max(), south=lat.min(), west=lon.min(), east=lon.max()) else: self.boundingbox = BoundingBox(north=max(self.boundingbox.north, lat.max()), south=min(self.boundingbox.south, lat.min()), west=min(self.boundingbox.west, lon.min()), east=max(self.boundingbox.east, lon.max())) def invalidate(self, proj): self.painter = BatchPainter() self.hotspots = HotspotManager() for feature in self.data['features']: if isfunction(self.color): self.painter.set_color(self.color(feature['properties'])) else: self.painter.set_color(self.color) if feature['geometry']['type'] == 'Polygon': for poly in feature['geometry']['coordinates']: poly = np.array(poly) x, y = proj.lonlat_to_screen(poly[:, 0], poly[:, 1]) if self.fill: self.painter.poly(x, y) else: self.painter.linestrip(x, y, self.linewidth, closed=True) if self.f_tooltip: self.hotspots.add_poly( x, y, self.f_tooltip(feature['properties'])) elif feature['geometry']['type'] == 'MultiPolygon': for multipoly in feature['geometry']['coordinates']: for poly in multipoly: poly = np.array(poly) x, y = proj.lonlat_to_screen(poly[:, 0], poly[:, 1]) if self.fill: self.painter.poly(x, y) else: self.painter.linestrip(x, y, self.linewidth, closed=True) if self.f_tooltip: self.hotspots.add_poly( x, y, self.f_tooltip(feature['properties'])) elif feature['geometry']['type'] == 'Point': lon, lat = feature['geometry']['coordinates'] x, y = proj.lonlat_to_screen(np.array([lon]), np.array([lat])) self.painter.points(x, y) elif feature['geometry']['type'] == 'LineString': line = np.array(feature['geometry']['coordinates']) x, y = proj.lonlat_to_screen(line[:, 0], line[:, 1]) self.painter.linestrip(x, y, self.linewidth, closed=False) else: print(('unknow geometry %s' % feature['geometry']['type'])) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspots.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) def bbox(self): if self.boundingbox: return self.boundingbox else: return BoundingBox.WORLD
class HistogramLayer(BaseLayer): def __init__(self, data, cmap='hot', alpha=220, colorscale='sqrt', binsize=16, show_tooltip=False, scalemin=0, scalemax=None, f_group=None, show_colorbar=True): """Create a 2D histogram :param data: data access object :param cmap: colormap name :param alpha: color alpha :param colorscale: scaling [lin, log, sqrt] :param binsize: size of the hist bins :param show_tooltip: if True, will show the value of bins on mouseover :param scalemin: min value for displaying a bin :param scalemax: max value for a bin :param f_group: function to apply to samples in the same bin. Default is to count :param show_colorbar: show colorbar :return: """ self.data = data self.cmap = colors.ColorMap(cmap, alpha=alpha) self.binsize = binsize self.show_tooltip = show_tooltip self.scalemin = scalemin self.scalemax = scalemax self.colorscale = colorscale self.f_group = f_group if self.f_group is None: self.f_group = lambda grp: len(grp) self.show_colorbar = show_colorbar def invalidate(self, proj): self.painter = BatchPainter() x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat']) self.data['_xbin'] = (x / self.binsize).astype(int) self.data['_ybin'] = (y / self.binsize).astype(int) uniquevalues = set([ tuple(row) for row in np.vstack([self.data['_xbin'], self.data['_ybin']]).T ]) results = {(v1,v2): self.f_group(self.data.where((self.data['_xbin'] == v1) & (self.data['_ybin'] == v2))) \ for v1, v2 in uniquevalues} del self.data['_xbin'] del self.data['_ybin'] self.hotspot = HotspotManager() if self.scalemax: self.vmax = self.scalemax else: self.vmax = max(results.values()) if len(results) > 0 else 0 if self.vmax >= 1: for (ix, iy), value in list(results.items()): if value > self.scalemin: self.painter.set_color( self.cmap.to_color(value, self.vmax, self.colorscale)) l = self.binsize rx = ix * self.binsize ry = iy * self.binsize self.painter.rect(rx, ry, rx + l, ry + l) if self.show_tooltip: self.hotspot.add_rect(rx, ry, l, l, 'Value: %d' % value) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw() picked = self.hotspot.pick(mouse_x, mouse_y) if picked: ui_manager.tooltip(picked) if self.show_colorbar: ui_manager.add_colorbar(self.cmap, self.vmax, self.colorscale) def bbox(self): return BoundingBox.from_points(lons=self.data['lon'], lats=self.data['lat'])
class AnimatedProcess(BaseLayer): def __init__(self, network, edgelists, show_addresses=False, save_frames=False, line_width=1.3): # Set network and edge lists. self.network = network self.edgelists = edgelists self.num_frames = len(self.edgelists) # Set flag specifying whether to show addresses. self.show_addresses = show_addresses # Set flag specifying whether to save frames. self.save_frames = save_frames # Set line width. self.line_width = line_width # Initialize state counter. self.count = 0 # Initialize geolocator for retrieving addresses. geolocator = Nominatim(user_agent='test') # Set coordinates of nodes and get addresses. self.node_coordinates = np.empty((2, network.number_of_nodes()), dtype=float) self.node_addresses = [] for idx, node in enumerate(network.nodes()): self.node_coordinates[:, idx] = np.array(self.network.nodes[node]['latlon']) if self.show_addresses: address = geolocator.reverse(self.node_coordinates[:, idx]).address self.node_addresses.append(address[:address.index(',', address.index(',') + 1)]) def invalidate(self, proj): self.x, self.y = proj.lonlat_to_screen(self.node_coordinates[1, :], self.node_coordinates[0, :]) def draw(self, proj, mouse_x, mouse_y, ui_manager): # Prepare edges for next frame. self.edge_src = np.empty((2, len(self.edgelists[self.count])), dtype=float) self.edge_dst = np.empty((2, len(self.edgelists[self.count])), dtype=float) for idx, edge in enumerate(self.edgelists[self.count]): self.edge_src[:, idx] = self.network.nodes[edge[0]]['latlon'] self.edge_dst[:, idx] = self.network.nodes[edge[1]]['latlon'] self.edge_src_trans_x, self.edge_src_trans_y = proj.lonlat_to_screen(self.edge_src[1, :], self.edge_src[0, :]) self.edge_dst_trans_x, self.edge_dst_trans_y = proj.lonlat_to_screen(self.edge_dst[1, :], self.edge_dst[0, :]) # Initialize painter, plot nodes and addresses. self.painter = BatchPainter() self.painter.points(self.x, self.y, point_size=10, rounded=True) if self.show_addresses: self.painter.labels(self.x, self.y, self.node_addresses, font_size=10, anchor_x='left') # Plot edges. self.painter.set_color([255, 0, 0]) self.painter.lines(self.edge_src_trans_x, self.edge_src_trans_y, self.edge_dst_trans_x, self.edge_dst_trans_y, width=self.line_width) # Draw and increment counter. self.painter.batch_draw() if self.count < len(self.edgelists) - 1: self.count += 1 self.count = self.count % self.num_frames print("count: {0}/{1}".format(self.count, self.num_frames)) # If saving frames. if self.save_frames: GeoplotlibApp.screenshot(f'./results/animation_frames/{self.count}.png')
class KDELayer(BaseLayer): def __init__(self, values, bw, cmap='hot', method='hist', scaling='sqrt', alpha=220, cut_below=None, clip_above=None, binsize=1, cmap_levels=10): """ Kernel density estimation visualization :param data: data access object :param bw: kernel bandwidth (in screen coordinates) :param cmap: colormap :param method: if kde use KDEMultivariate from statsmodel, which provides a more accurate but much slower estimation. If hist, estimates density applying gaussian smoothing on a 2D histogram, which is much faster but less accurate :param scaling: colorscale, lin log or sqrt :param alpha: color alpha :param cut_below: densities below cut_below are not drawn :param clip_above: defines the max value for the colorscale :param binsize: size of the bins for hist estimator :param cmap_levels: discretize colors into cmap_levels """ self.values = values self.bw = bw self.cmap = colors.ColorMap(cmap, alpha=alpha, levels=cmap_levels) self.method = method self.scaling = scaling self.cut_below = cut_below self.clip_above = clip_above self.binsize = binsize def _get_grid(self, proj): west, north = proj.lonlat_to_screen([proj.bbox().west], [proj.bbox().north]) east, south = proj.lonlat_to_screen([proj.bbox().east], [proj.bbox().south]) xgrid = np.arange(west, east, self.binsize) ygrid = np.arange(south, north, self.binsize) return xgrid, ygrid def invalidate(self, proj): self.painter = BatchPainter() xv, yv = proj.lonlat_to_screen(self.values['lon'], self.values['lat']) rects_vertices = [] rects_colors = [] if self.method == 'kde': try: import statsmodels.api as sm except: raise Exception('KDE requires statsmodel') kde_res = sm.nonparametric.KDEMultivariate(data=[xv, yv], var_type='cc', bw=self.bw) xgrid, ygrid = self._get_grid(proj) xmesh, ymesh = np.meshgrid(xgrid,ygrid) grid_coords = np.append(xmesh.reshape(-1,1), ymesh.reshape(-1,1),axis=1) z = kde_res.pdf(grid_coords.T) z = z.reshape(len(ygrid), len(xgrid)) # np.save('z.npy', z) # z = np.load('z.npy') print('smallest non-zero density:', z[z > 0][0]) print('max density:', z.max()) if self.cut_below is None: zmin = z[z > 0][0] else: zmin = self.cut_below if self.clip_above is None: zmax = z.max() else: zmax = self.clip_above for ix in range(len(xgrid)-1): for iy in range(len(ygrid)-1): if z[iy, ix] > zmin: rects_vertices.append((xgrid[ix], ygrid[iy], xgrid[ix+1], ygrid[iy+1])) rects_colors.append(self.cmap.to_color(z[iy, ix], zmax, self.scaling)) elif self.method == 'hist': try: from scipy.ndimage import gaussian_filter except: raise Exception('KDE requires scipy') xgrid, ygrid = self._get_grid(proj) H, _, _ = np.histogram2d(yv, xv, bins=(ygrid, xgrid)) H = gaussian_filter(H, sigma=self.bw) print('smallest non-zero count', H[H > 0][0]) print('max count:', H.max()) if self.cut_below is None: Hmin = H[H > 0][0] else: Hmin = self.cut_below if self.clip_above is None: Hmax = H.max() else: Hmax = self.clip_above if self.scaling == 'ranking': from statsmodels.distributions.empirical_distribution import ECDF ecdf = ECDF(H.flatten()) for ix in range(len(xgrid)-2): for iy in range(len(ygrid)-2): if H[iy, ix] > Hmin: rects_vertices.append((xgrid[ix], ygrid[iy], xgrid[ix+1], ygrid[iy+1])) if self.scaling == 'ranking': rects_colors.append(self.cmap.to_color(ecdf(H[iy, ix]) - ecdf(Hmin), 1 - ecdf(Hmin), 'lin')) else: rects_colors.append(self.cmap.to_color(H[iy, ix], Hmax, self.scaling)) else: raise Exception('method not supported') self.painter.batch_rects(rects_vertices, rects_colors) def draw(self, proj, mouse_x, mouse_y, ui_manager): self.painter.batch_draw()