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