|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
00001 """! 00002 @package modules.histogram 00003 00004 Plotting histogram based on d.histogram 00005 00006 Classes: 00007 - histogram::BufferedWindow 00008 - histogram::HistogramFrame 00009 - histogram::HistogramToolbar 00010 00011 (C) 2007, 2010-2011 by the GRASS Development Team 00012 00013 This program is free software under the GNU General Public License 00014 (>=v2). Read the file COPYING that comes with GRASS for details. 00015 00016 @author Michael Barton 00017 @author Various updates by Martin Landa <landa.martin gmail.com> 00018 """ 00019 00020 import os 00021 00022 import wx 00023 00024 from core import globalvar 00025 from core.render import Map 00026 from gui_core.forms import GUI 00027 from mapdisp.gprint import PrintOptions 00028 from core.utils import GetLayerNameFromCmd 00029 from gui_core.dialogs import GetImageHandlers, ImageSizeDialog 00030 from gui_core.preferences import DefaultFontDialog 00031 from core.debug import Debug 00032 from core.gcmd import GError 00033 from gui_core.toolbars import BaseToolbar, BaseIcons 00034 00035 class BufferedWindow(wx.Window): 00036 """!A Buffered window class. 00037 00038 When the drawing needs to change, you app needs to call the 00039 UpdateHist() method. Since the drawing is stored in a bitmap, you 00040 can also save the drawing to file by calling the 00041 SaveToFile(self,file_name,file_type) method. 00042 """ 00043 def __init__(self, parent, id = wx.ID_ANY, 00044 style = wx.NO_FULL_REPAINT_ON_RESIZE, 00045 Map = None, **kwargs): 00046 00047 wx.Window.__init__(self, parent, id = id, style = style, **kwargs) 00048 00049 self.parent = parent 00050 self.Map = Map 00051 self.mapname = self.parent.mapname 00052 00053 # 00054 # Flags 00055 # 00056 self.render = True # re-render the map from GRASS or just redraw image 00057 self.resize = False # indicates whether or not a resize event has taken place 00058 self.dragimg = None # initialize variable for map panning 00059 self.pen = None # pen for drawing zoom boxes, etc. 00060 00061 # 00062 # Event bindings 00063 # 00064 self.Bind(wx.EVT_PAINT, self.OnPaint) 00065 self.Bind(wx.EVT_SIZE, self.OnSize) 00066 self.Bind(wx.EVT_IDLE, self.OnIdle) 00067 00068 # 00069 # Render output objects 00070 # 00071 self.mapfile = None # image file to be rendered 00072 self.img = "" # wx.Image object (self.mapfile) 00073 00074 self.imagedict = {} # images and their PseudoDC ID's for painting and dragging 00075 00076 self.pdc = wx.PseudoDC() 00077 self._buffer = '' # will store an off screen empty bitmap for saving to file 00078 00079 # make sure that extents are updated at init 00080 self.Map.region = self.Map.GetRegion() 00081 self.Map.SetRegion() 00082 00083 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x:None) 00084 00085 def Draw(self, pdc, img = None, drawid = None, pdctype = 'image', coords = [0,0,0,0]): 00086 """!Draws histogram or clears window 00087 """ 00088 if drawid == None: 00089 if pdctype == 'image' : 00090 drawid = imagedict[img] 00091 elif pdctype == 'clear': 00092 drawid == None 00093 else: 00094 drawid = wx.NewId() 00095 else: 00096 pdc.SetId(drawid) 00097 00098 pdc.BeginDrawing() 00099 00100 Debug.msg (3, "BufferedWindow.Draw(): id=%s, pdctype=%s, coord=%s" % (drawid, pdctype, coords)) 00101 00102 if pdctype == 'clear': # erase the display 00103 bg = wx.WHITE_BRUSH 00104 pdc.SetBackground(bg) 00105 pdc.Clear() 00106 self.Refresh() 00107 pdc.EndDrawing() 00108 return 00109 00110 if pdctype == 'image': 00111 bg = wx.TRANSPARENT_BRUSH 00112 pdc.SetBackground(bg) 00113 bitmap = wx.BitmapFromImage(img) 00114 w,h = bitmap.GetSize() 00115 pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map 00116 pdc.SetIdBounds(drawid, (coords[0],coords[1],w,h)) 00117 00118 pdc.EndDrawing() 00119 self.Refresh() 00120 00121 def OnPaint(self, event): 00122 """!Draw psuedo DC to buffer 00123 """ 00124 dc = wx.BufferedPaintDC(self, self._buffer) 00125 00126 # use PrepareDC to set position correctly 00127 self.PrepareDC(dc) 00128 # we need to clear the dc BEFORE calling PrepareDC 00129 bg = wx.Brush(self.GetBackgroundColour()) 00130 dc.SetBackground(bg) 00131 dc.Clear() 00132 # create a clipping rect from our position and size 00133 # and the Update Region 00134 rgn = self.GetUpdateRegion() 00135 r = rgn.GetBox() 00136 # draw to the dc using the calculated clipping rect 00137 self.pdc.DrawToDCClipped(dc,r) 00138 00139 def OnSize(self, event): 00140 """!Init image size to match window size 00141 """ 00142 # set size of the input image 00143 self.Map.width, self.Map.height = self.GetClientSize() 00144 00145 # Make new off screen bitmap: this bitmap will always have the 00146 # current drawing in it, so it can be used to save the image to 00147 # a file, or whatever. 00148 self._buffer = wx.EmptyBitmap(self.Map.width, self.Map.height) 00149 00150 # get the image to be rendered 00151 self.img = self.GetImage() 00152 00153 # update map display 00154 if self.img and self.Map.width + self.Map.height > 0: # scale image during resize 00155 self.img = self.img.Scale(self.Map.width, self.Map.height) 00156 self.render = False 00157 self.UpdateHist() 00158 00159 # re-render image on idle 00160 self.resize = True 00161 00162 def OnIdle(self, event): 00163 """!Only re-render a histogram image from GRASS during idle 00164 time instead of multiple times during resizing. 00165 """ 00166 if self.resize: 00167 self.render = True 00168 self.UpdateHist() 00169 event.Skip() 00170 00171 def SaveToFile(self, FileName, FileType, width, height): 00172 """!This will save the contents of the buffer to the specified 00173 file. See the wx.Windows docs for wx.Bitmap::SaveFile for the 00174 details 00175 """ 00176 busy = wx.BusyInfo(message=_("Please wait, exporting image..."), 00177 parent=self) 00178 wx.Yield() 00179 00180 self.Map.ChangeMapSize((width, height)) 00181 ibuffer = wx.EmptyBitmap(max(1, width), max(1, height)) 00182 self.Map.Render(force=True, windres = True) 00183 img = self.GetImage() 00184 self.Draw(self.pdc, img, drawid = 99) 00185 dc = wx.BufferedPaintDC(self, ibuffer) 00186 dc.Clear() 00187 self.PrepareDC(dc) 00188 self.pdc.DrawToDC(dc) 00189 ibuffer.SaveFile(FileName, FileType) 00190 00191 busy.Destroy() 00192 00193 def GetImage(self): 00194 """!Converts files to wx.Image 00195 """ 00196 if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \ 00197 os.path.getsize(self.Map.mapfile): 00198 img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY) 00199 else: 00200 img = None 00201 00202 self.imagedict[img] = 99 # set image PeudoDC ID 00203 return img 00204 00205 def UpdateHist(self, img = None): 00206 """!Update canvas if histogram options changes or window 00207 changes geometry 00208 """ 00209 Debug.msg (2, "BufferedWindow.UpdateHist(%s): render=%s" % (img, self.render)) 00210 oldfont = "" 00211 oldencoding = "" 00212 00213 if self.render: 00214 # render new map images 00215 # set default font and encoding environmental variables 00216 if "GRASS_FONT" in os.environ: 00217 oldfont = os.environ["GRASS_FONT"] 00218 if self.parent.font != "": os.environ["GRASS_FONT"] = self.parent.font 00219 if "GRASS_ENCODING" in os.environ: 00220 oldencoding = os.environ["GRASS_ENCODING"] 00221 if self.parent.encoding != None and self.parent.encoding != "ISO-8859-1": 00222 os.environ[GRASS_ENCODING] = self.parent.encoding 00223 00224 # using active comp region 00225 self.Map.GetRegion(update = True) 00226 00227 self.Map.width, self.Map.height = self.GetClientSize() 00228 self.mapfile = self.Map.Render(force = self.render) 00229 self.img = self.GetImage() 00230 self.resize = False 00231 00232 if not self.img: return 00233 try: 00234 id = self.imagedict[self.img] 00235 except: 00236 return 00237 00238 # paint images to PseudoDC 00239 self.pdc.Clear() 00240 self.pdc.RemoveAll() 00241 self.Draw(self.pdc, self.img, drawid = id) # draw map image background 00242 00243 self.resize = False 00244 00245 # update statusbar 00246 # Debug.msg (3, "BufferedWindow.UpdateHist(%s): region=%s" % self.Map.region) 00247 self.Map.SetRegion() 00248 self.parent.statusbar.SetStatusText("Image/Raster map <%s>" % self.parent.mapname) 00249 00250 # set default font and encoding environmental variables 00251 if oldfont != "": 00252 os.environ["GRASS_FONT"] = oldfont 00253 if oldencoding != "": 00254 os.environ["GRASS_ENCODING"] = oldencoding 00255 00256 def EraseMap(self): 00257 """!Erase the map display 00258 """ 00259 self.Draw(self.pdc, pdctype = 'clear') 00260 00261 class HistogramFrame(wx.Frame): 00262 """!Main frame for hisgram display window. Uses d.histogram 00263 rendered onto canvas 00264 """ 00265 def __init__(self, parent = None, id = wx.ID_ANY, 00266 title = _("GRASS GIS Histogramming Tool (d.histogram)"), 00267 size = wx.Size(500, 350), 00268 style = wx.DEFAULT_FRAME_STYLE, **kwargs): 00269 wx.Frame.__init__(self, parent, id, title, size = size, style = style, **kwargs) 00270 self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO)) 00271 00272 self.Map = Map() # instance of render.Map to be associated with display 00273 self.layer = None # reference to layer with histogram 00274 00275 # Init variables 00276 self.params = {} # previously set histogram parameters 00277 self.propwin = '' # ID of properties dialog 00278 00279 self.font = "" 00280 self.encoding = 'ISO-8859-1' # default encoding for display fonts 00281 00282 self.toolbar = HistogramToolbar(parent = self) 00283 self.SetToolBar(self.toolbar) 00284 00285 # Add statusbar 00286 self.mapname = '' 00287 self.statusbar = self.CreateStatusBar(number = 1, style = 0) 00288 # self.statusbar.SetStatusWidths([-2, -1]) 00289 hist_frame_statusbar_fields = ["Histogramming %s" % self.mapname] 00290 for i in range(len(hist_frame_statusbar_fields)): 00291 self.statusbar.SetStatusText(hist_frame_statusbar_fields[i], i) 00292 00293 # Init map display 00294 self.InitDisplay() # initialize region values 00295 00296 # initialize buffered DC 00297 self.HistWindow = BufferedWindow(self, id = wx.ID_ANY, Map = self.Map) # initialize buffered DC 00298 00299 # Bind various events 00300 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 00301 00302 # Init print module and classes 00303 self.printopt = PrintOptions(self, self.HistWindow) 00304 00305 # Add layer to the map 00306 self.layer = self.Map.AddLayer(type = "command", name = 'histogram', command = ['d.histogram'], 00307 l_active = False, l_hidden = False, l_opacity = 1, l_render = False) 00308 00309 def InitDisplay(self): 00310 """!Initialize histogram display, set dimensions and region 00311 """ 00312 self.width, self.height = self.GetClientSize() 00313 self.Map.geom = self.width, self.height 00314 00315 def OnOptions(self, event): 00316 """!Change histogram settings""" 00317 cmd = ['d.histogram'] 00318 if self.mapname != '': 00319 cmd.append('map=%s' % self.mapname) 00320 00321 GUI(parent = self).ParseCommand(cmd, 00322 completed = (self.GetOptData, None, self.params)) 00323 00324 def GetOptData(self, dcmd, layer, params, propwin): 00325 """!Callback method for histogram command generated by dialog 00326 created in menuform.py 00327 """ 00328 if dcmd: 00329 name, found = GetLayerNameFromCmd(dcmd, fullyQualified = True, 00330 layerType = 'raster') 00331 if not found: 00332 GError(parent = propwin, 00333 message = _("Raster map <%s> not found") % name) 00334 return 00335 00336 self.SetHistLayer(name) 00337 self.params = params 00338 self.propwin = propwin 00339 00340 self.HistWindow.UpdateHist() 00341 00342 def SetHistLayer(self, name): 00343 """!Set histogram layer 00344 """ 00345 self.mapname = name 00346 00347 self.layer = self.Map.ChangeLayer(layer = self.layer, 00348 command = [['d.histogram', 'map=%s' % self.mapname],], 00349 active = True) 00350 00351 return self.layer 00352 00353 def SetHistFont(self, event): 00354 """!Set font for histogram. If not set, font will be default 00355 display font. 00356 """ 00357 dlg = DefaultFontDialog(parent = self, id = wx.ID_ANY, 00358 title = _('Select font for histogram text')) 00359 dlg.fontlb.SetStringSelection(self.font, True) 00360 00361 if dlg.ShowModal() == wx.ID_CANCEL: 00362 dlg.Destroy() 00363 return 00364 00365 # set default font type, font, and encoding to whatever selected in dialog 00366 if dlg.font != None: 00367 self.font = dlg.font 00368 if dlg.encoding != None: 00369 self.encoding = dlg.encoding 00370 00371 dlg.Destroy() 00372 self.HistWindow.UpdateHist() 00373 00374 def OnErase(self, event): 00375 """!Erase the histogram display 00376 """ 00377 self.HistWindow.Draw(self.HistWindow.pdc, pdctype = 'clear') 00378 00379 def OnRender(self, event): 00380 """!Re-render histogram 00381 """ 00382 self.HistWindow.UpdateHist() 00383 00384 def GetWindow(self): 00385 """!Get buffered window""" 00386 return self.HistWindow 00387 00388 def SaveToFile(self, event): 00389 """!Save to file 00390 """ 00391 filetype, ltype = GetImageHandlers(self.HistWindow.img) 00392 00393 # get size 00394 dlg = ImageSizeDialog(self) 00395 dlg.CentreOnParent() 00396 if dlg.ShowModal() != wx.ID_OK: 00397 dlg.Destroy() 00398 return 00399 width, height = dlg.GetValues() 00400 dlg.Destroy() 00401 00402 # get filename 00403 dlg = wx.FileDialog(parent = self, 00404 message = _("Choose a file name to save the image " 00405 "(no need to add extension)"), 00406 wildcard = filetype, 00407 style=wx.SAVE | wx.FD_OVERWRITE_PROMPT) 00408 00409 if dlg.ShowModal() == wx.ID_OK: 00410 path = dlg.GetPath() 00411 if not path: 00412 dlg.Destroy() 00413 return 00414 00415 base, ext = os.path.splitext(path) 00416 fileType = ltype[dlg.GetFilterIndex()]['type'] 00417 extType = ltype[dlg.GetFilterIndex()]['ext'] 00418 if ext != extType: 00419 path = base + '.' + extType 00420 00421 self.HistWindow.SaveToFile(path, fileType, 00422 width, height) 00423 00424 self.HistWindow.UpdateHist() 00425 dlg.Destroy() 00426 00427 def PrintMenu(self, event): 00428 """!Print options and output menu 00429 """ 00430 point = wx.GetMousePosition() 00431 printmenu = wx.Menu() 00432 # Add items to the menu 00433 setup = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Page setup')) 00434 printmenu.AppendItem(setup) 00435 self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup) 00436 00437 preview = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print preview')) 00438 printmenu.AppendItem(preview) 00439 self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview) 00440 00441 doprint = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print display')) 00442 printmenu.AppendItem(doprint) 00443 self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint) 00444 00445 # Popup the menu. If an item is selected then its handler 00446 # will be called before PopupMenu returns. 00447 self.PopupMenu(printmenu) 00448 printmenu.Destroy() 00449 00450 def OnQuit(self, event): 00451 self.Close(True) 00452 00453 def OnCloseWindow(self, event): 00454 """!Window closed 00455 Also remove associated rendered images 00456 """ 00457 try: 00458 self.propwin.Close(True) 00459 except: 00460 pass 00461 self.Map.Clean() 00462 self.Destroy() 00463 00464 class HistogramToolbar(BaseToolbar): 00465 """!Histogram toolbar (see histogram.py) 00466 """ 00467 def __init__(self, parent): 00468 BaseToolbar.__init__(self, parent) 00469 00470 self.InitToolbar(self._toolbarData()) 00471 00472 # realize the toolbar 00473 self.Realize() 00474 00475 def _toolbarData(self): 00476 """!Toolbar data""" 00477 return self._getToolbarData((('histogram', BaseIcons["histogramD"], 00478 self.parent.OnOptions), 00479 ('render', BaseIcons["display"], 00480 self.parent.OnRender), 00481 ('erase', BaseIcons["erase"], 00482 self.parent.OnErase), 00483 ('font', BaseIcons["font"], 00484 self.parent.SetHistFont), 00485 (None, ), 00486 ('save', BaseIcons["saveFile"], 00487 self.parent.SaveToFile), 00488 ('hprint', BaseIcons["print"], 00489 self.parent.PrintMenu), 00490 (None, ), 00491 ('quit', BaseIcons["quit"], 00492 self.parent.OnQuit)) 00493 )