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