def __init__(self, parent, cachedir='.cache', *args, **kwargs): """Init the MapPanel. @param cachedir: directory to store downloaded OSM tiles """ wx.Panel.__init__(self, parent, *args, **kwargs) self.SetBackgroundColour("Light Grey") self.cache_dir = cachedir try: os.mkdir(self.cache_dir) print 'No cache folder found. Creating it.' except OSError: print 'Found cache folder.' self.placeholder = wx.EmptyImage(*self.Parent.GetSize()) self.placeholder.ConvertColourToAlpha(0, 0, 0) self.placeholder = self.placeholder.ConvertToBitmap() self.tiles = {} self.tiles_used = {} self.tileloader = TileLoader(tile_list=self.tiles, cache_dir=cachedir) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self._center_lat = 52.382463 self._center_lon = 9.717836 self._center_x = 0 self._center_y = 0 self.rel_pos_x = 0 self.rel_pos_y = 0 self._zoom = 17 self.create_map() self._distance_x = 0 self._distance_y = 0 self.calc_geotilesize() self.points = [] self.point_colours = [] self.rectangles = [] self.rectangle_colours = [] self.greyedOverlay = None #: tuple [rga value, alpha] self.current_point = 0 self.current_rectangle = 0 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.async_timer = wx.PyTimer(self.updateTiles) self.async_timer.Start(500, False)
class MapPanel(wx.Panel): """Basic wx.Panel for viewing an OSM map. Panel shows map tiles and optional markers and bounding boxes. @author: B. Henne @author: P. Tute """ def __init__(self, parent, cachedir='.cache', *args, **kwargs): """Init the MapPanel. @param cachedir: directory to store downloaded OSM tiles """ wx.Panel.__init__(self, parent, *args, **kwargs) self.SetBackgroundColour("Light Grey") self.cache_dir = cachedir try: os.mkdir(self.cache_dir) print 'No cache folder found. Creating it.' except OSError: print 'Found cache folder.' self.placeholder = wx.EmptyImage(*self.Parent.GetSize()) self.placeholder.ConvertColourToAlpha(0, 0, 0) self.placeholder = self.placeholder.ConvertToBitmap() self.tiles = {} self.tiles_used = {} self.tileloader = TileLoader(tile_list=self.tiles, cache_dir=cachedir) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self._center_lat = 52.382463 self._center_lon = 9.717836 self._center_x = 0 self._center_y = 0 self.rel_pos_x = 0 self.rel_pos_y = 0 self._zoom = 17 self.create_map() self._distance_x = 0 self._distance_y = 0 self.calc_geotilesize() self.points = [] self.point_colours = [] self.rectangles = [] self.rectangle_colours = [] self.greyedOverlay = None #: tuple [rga value, alpha] self.current_point = 0 self.current_rectangle = 0 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.async_timer = wx.PyTimer(self.updateTiles) self.async_timer.Start(500, False) def updateTiles(self): """Loads newly downloaded tiles from file""" self.tileloader.load_images() self.Refresh() def OnKeyDown(self, e): keycode = e.GetKeyCode() print keycode if keycode == 43: # +: zoom in self.set_zoom(self.get_zoom() + 1) self.Refresh() elif keycode == 45: # -: zoom out self.set_zoom(self.get_zoom() - 1) self.Refresh() elif keycode == 80: # p: walk through points if len(self.points) > 0: self.current_point = (self.current_point + 1) % len(self.points) self.center = self.points[self.current_point] self.Refresh() elif keycode == 82: # r: walk through rectangles if len(self.rectangles) > 0: self.current_rectangle = (self.current_rectangle + 1) % len(self.rectangles) r = self.rectangles[self.current_rectangle] self.center = (r[0] + (r[2] - r[0]) / 2, r[1] + (r[3] - r[1]) / 2) self.Refresh() elif (keycode == wx.WXK_UP) or (keycode == wx.WXK_NUMPAD_UP): lat, lon = self.center self.center = (lat+self._distance_y/2, lon) self.Refresh() elif (keycode == wx.WXK_DOWN) or (keycode == wx.WXK_NUMPAD_DOWN): lat, lon = self.center self.center = (lat-self._distance_y/2, lon) self.Refresh() elif (keycode == wx.WXK_LEFT) or (keycode == wx.WXK_NUMPAD_LEFT): lat, lon = self.center self.center = (lat, lon-self._distance_x/2) self.Refresh() elif (keycode == wx.WXK_RIGHT) or (keycode == wx.WXK_NUMPAD_RIGHT): lat, lon = self.center self.center = (lat, lon+self._distance_x/2) self.Refresh() elif keycode == 49: self.zoom = 11 self.Refresh() elif keycode == 50: self.zoom = 12 self.Refresh() elif keycode == 51: self.zoom = 13 self.Refresh() elif keycode == 52: self.zoom = 14 self.Refresh() elif keycode == 53: self.zoom = 15 self.Refresh() elif keycode == 54: self.zoom = 16 self.Refresh() elif keycode == 55: self.zoom = 17 self.Refresh() elif keycode == 56: self.zoom = 18 self.Refresh() e.Skip() def OnPaint(self, Event): self.tileloader.load_images() dc = wx.AutoBufferedPaintDCFactory(self) dc.Clear() size = self.GetSize() for coord in self.tiles_used: image = self.tiles[self.tiles_used[coord]] x = coord[0] y = coord[1] dc.DrawBitmap(image, x * TILE_SIZE - self.rel_pos_x + size[0] / 2 - TILE_SIZE / 2, # substract half panel size to center on middle of tile... y * TILE_SIZE - self.rel_pos_y + size[1] / 2 - TILE_SIZE / 2) # wx.GCDC for alpha channel support; map is drawn without alpha support to work in Windows... dc = wx.GCDC(dc) if self.points: self.draw_point_list(dc, self.points, self.point_colours) if self.rectangles: self.draw_rectangle_list(dc, self.rectangles, self.rectangle_colours) if self.greyedOverlay is not None: dc.DrawBitmap(wx.EmptyBitmapRGBA(size[0], size[1], self.greyedOverlay[0], self.greyedOverlay[0], self.greyedOverlay[0], self.greyedOverlay[1]), 0, 0) del dc def OnSize(self, event): self.set_center((self._center_lat, self._center_lon)) self.create_map() self.Refresh() def get_zoom(self): return self._zoom def set_zoom(self, zoom): self._zoom = min(max(0, zoom), 18) # 0 <= zoom <= 18 self.create_map() self.calc_geotilesize() zoom = property(get_zoom, set_zoom) def get_lat(self): return self._center_lat def set_lat(self, lat): self._center_lat = min(max(-90, lat), 90) # -90 <= lat <= 90 self.create_map() lat = property(get_lat, set_lat) def get_lon(self): return self._center_lon def set_lon(self, lon): self._center_lon = min(max(-180, lat), 180) # -180 <= lat <= 180 self.create_map() lon = property(get_lon, set_lon) def get_center(self): return [self._center_lat, self._center_lon] def set_center(self, coords): self._center_lat = min(max(-90, coords[0]), 90) # -90 <= lat <= 90 self._center_lon = min(max(-180, coords[1]), 180) # -180 <= lat <= 180 self.create_map() center = property(get_center, set_center) def calc_geotilesize(self): """Calculates geographical size of a tile at current zoom.""" s, w, n, e = tiles.tileEdges(self._center_x, self._center_y, self._zoom) self._distance_x = abs(w - e) self._distance_y = abs(n - s) def create_map(self): """Calculate all necessary coordinates and values, and get the right images.""" # calculate tile number and borders of the tile in lat/lon self._center_x_float, self._center_y_float = calc.deg2num(self._center_lat, self._center_lon, self._zoom) self._center_x = int(self._center_x_float) self._center_y = int(self._center_y_float) # calculate offset needed for centering on the desired coordinates. # Half of TILE_SIZE is substracted because when drawing we center on the middle of the tile. self.rel_pos_x = TILE_SIZE * (self._center_x_float - self._center_x) - TILE_SIZE / 2 self.rel_pos_y = TILE_SIZE * (self._center_y_float - self._center_y) - TILE_SIZE / 2 # determine, how many tiles are necessary around the center one... # this should be the tiles needed to fill the screen +1 to avoid borders width, height = self.GetSize() self.number_of_tiles = ((max(width, height) / TILE_SIZE) / 2) + 1 # and load the tiles for y in xrange(-self.number_of_tiles, self.number_of_tiles + 1): for x in xrange(-self.number_of_tiles, self.number_of_tiles + 1): # absolute osm-values absolute_x = self._center_x + x absolute_y = self._center_y + y if (absolute_x, absolute_y, self.zoom) not in self.tiles: # image is not in memory...load it self.get_image(absolute_x, absolute_y, self.zoom) # add tile to the tiles that will be drawn... # (0, 0) is our center tile and will be in the middle of the panel self.tiles_used[(x, y)] = (absolute_x, absolute_y, self.zoom) def get_image(self, x, y, z, layer='mapnik'): """Load an image from the cache folder or download it. Try to load from cache-folder first, download and cache if no image was found. The image is placed in self.tiles by this method or by the TileLoader.load_images() after downloading. @param x: OSM-tile number in x-direction @type x: int @param y: OSM-tile number in y-direction @type y: int @param z: OSM-zoom @type z: int in range [0, 18] @param layer: The used map layer (default 'mapnik') @type layer: string (one of 'tah', 'oam' and 'mapnik') """ url = tiles.tileURL(x, y, z, layer) parts = url.split('/')[-4:] if not os.path.exists(os.path.join(self.cache_dir, *parts)): # Image is not cached yet. Create necessary folders and download image. self.tileloader.enqueue_tile(x, y, z, layer) return image = wx.EmptyBitmap(1, 1) # Create a bitmap container object. The size values are dummies. image.LoadFile(os.path.join(self.cache_dir, *parts), wx.BITMAP_TYPE_ANY) # Load it with a file image. self.tiles[(x, y, z)] = image def draw_point(self, dc, lat, lon, wx_colour): """Draw a single point. @param dc: a wx.DC that will be used for drawing. @type dc: wx.DC @param lat: latitude of the point to be drawn on the map @param lon: longitude of the point to be drawn on the map @param wx_colour: the colour of the point @type wx_colour: wx colour, e. g. wx.RED """ x, y = calc.latlon_to_xy(lat, lon, self.zoom, self) dc.SetPen(wx.Pen(wx_colour, 2)) dc.DrawRectangle(x - POINT_SIZE / 2 + 1 - self.rel_pos_x, y - POINT_SIZE / 2 + 1 - self.rel_pos_y, POINT_SIZE, POINT_SIZE) def draw_rectangle_list(self, dc, rectangles, wx_colours): """Draw a list of rectangles all at once. @param dc: a wx.DC that will be used for drawing. @type dc: wx.DC @param coords: a list containing (lat, lon, lat, lon) sequences representing top left and bottom right corner of each rectangle. @param wx_colours: a list of at least as many colours as there are rectangles. @type wx_colours: iterable containing wx colours, e. g. wx.RED @param filled: if this is True, all rectangles will be drawn with an (not completely opaque) filling. """ rects = [] pens = [] brushes = [] for colour, line in enumerate(rectangles): x_left, y_top = calc.latlon_to_xy(line[0], line[1], self.zoom, self) x_right, y_bottom = calc.latlon_to_xy(line[2], line[3], self.zoom, self) width = abs(x_right - x_left) height = abs(y_bottom - y_top) rects.append([x_left - self.rel_pos_x, y_top - self.rel_pos_y, width, height]) pen = wx.Pen(wx_colours[colour], RECTANGLE_LINE_WIDTH) pens.append(pen) if RECTANGLE_FILL: colour = pen.GetColour().Get(True)[:-1] + (RECTANGLE_FILL_ALPHA,) # take colour from pen, but replace alpha value brush = wx.Brush(colour) else: brush = wx.Brush(wx_colours[colour], wx.TRANSPARENT) brushes.append(brush) dc.DrawRectangleList(rects, pens, brushes) def draw_point_list(self, dc, coords, wx_colours): """Draw a list of points all at once. @param dc: a wx.DC that will be used for drawing. @type dc: wx.DC @param coords: a list containing (lat, lon) tuples. @param wx_colours: a list of at least as many colours as there are coord-pairs. @type wx_colours: iterable containing wx colours, e. g. wx.RED """ draw_list = [] pen_list = [] for colour, coord in enumerate(coords): #self.draw_point(dc, coord[0], coord[1], wx_colours[colour]) x, y = calc.latlon_to_xy(coord[0], coord[1], self.zoom, self) draw_list.append([x - POINT_SIZE / 2 + 1 - self.rel_pos_x, y - POINT_SIZE / 2 + 1 - self.rel_pos_y, POINT_SIZE, POINT_SIZE]) pen_list.append(wx.Pen(wx_colours[colour], 2)) dc.DrawRectangleList(draw_list, pen_list)