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 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 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 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 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 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 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 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 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 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