GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
histogram.py
Go to the documentation of this file.
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                                     )