예제 #1
0
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()
예제 #2
0
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()
예제 #3
0
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()
예제 #4
0
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()
예제 #5
0
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))
예제 #6
0
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
예제 #7
0
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'])
예제 #8
0
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
예제 #9
0
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'])
예제 #10
0
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
예제 #11
0
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