GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
profile.py
Go to the documentation of this file.
00001 """!
00002 @package wxplot.profile
00003 
00004 @brief Profiling using PyPlot
00005 
00006 Classes:
00007  - profile::ProfileFrame
00008  - profile::ProfileToolbar
00009 
00010 (C) 2011 by the GRASS Development Team
00011 
00012 This program is free software under the GNU General Public License
00013 (>=v2). Read the file COPYING that comes with GRASS for details.
00014 
00015 @author Michael Barton, Arizona State University
00016 """
00017 
00018 import os
00019 import sys
00020 import math
00021 
00022 import wx
00023 import wx.lib.plot as plot
00024 
00025 import grass.script as grass
00026 
00027 try:
00028     import numpy
00029 except ImportError:
00030     msg = _("This module requires the NumPy module, which could not be "
00031             "imported. It probably is not installed (it's not part of the "
00032             "standard Python distribution). See the Numeric Python site "
00033             "(http://numpy.scipy.org) for information on downloading source or "
00034             "binaries.")
00035     print >> sys.stderr, "wxplot.py: " + msg
00036 
00037 from wxplot.base       import BasePlotFrame, PlotIcons
00038 from gui_core.toolbars import BaseToolbar, BaseIcons
00039 from wxplot.dialogs    import ProfileRasterDialog, PlotStatsFrame
00040 from core.gcmd         import RunCommand
00041 
00042 class ProfileFrame(BasePlotFrame):
00043     """!Mainframe for displaying profile of one or more raster maps. Uses wx.lib.plot.
00044     """
00045     def __init__(self, parent, id = wx.ID_ANY, style = wx.DEFAULT_FRAME_STYLE,
00046                  size = wx.Size(700, 400),
00047                  rasterList = [], **kwargs):
00048         BasePlotFrame.__init__(self, parent, size = size, **kwargs)
00049 
00050         self.toolbar = ProfileToolbar(parent = self)
00051         self.SetToolBar(self.toolbar)
00052         self.SetTitle(_("GRASS Profile Analysis Tool"))
00053         
00054         #
00055         # Init variables
00056         #
00057         self.rasterList = rasterList
00058         self.plottype = 'profile'
00059         self.coordstr = ''              # string of coordinates for r.profile
00060         self.seglist = []               # segment endpoint list
00061         self.ppoints = ''               # segment endpoints data         
00062         self.transect_length = 0.0      # total transect length        
00063         self.ptitle = _('Profile of')   # title of window
00064         self.colorList =  ["blue", "red", "green", "yellow", "magenta", "cyan",
00065                            "aqua", "black", "grey", "orange", "brown", "purple", "violet",
00066                            "indigo"]
00067         
00068         if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
00069             self.raster = self.InitRasterOpts(self.rasterList, self.plottype)
00070         else:
00071             self.raster = {}
00072         
00073         self._initOpts()
00074         
00075         # determine units (axis labels)
00076         if self.parent.Map.projinfo['units'] != '':
00077             self.xlabel = _('Distance (%s)') % self.parent.Map.projinfo['units']
00078         else:
00079             self.xlabel = _("Distance along transect")
00080         self.ylabel = _("Cell values")
00081         
00082     def _initOpts(self):
00083         """!Initialize plot options
00084         """
00085         self.InitPlotOpts('profile')
00086 
00087     def OnDrawTransect(self, event):
00088         """!Draws transect to profile in map display
00089         """
00090         self.mapwin.polycoords = []
00091         self.seglist = []
00092         self.mapwin.ClearLines(self.mapwin.pdc)
00093         self.ppoints = ''
00094 
00095         self.parent.SetFocus()
00096         self.parent.Raise()
00097         
00098         self.mapwin.mouse['use'] = 'profile'
00099         self.mapwin.mouse['box'] = 'line'
00100         self.mapwin.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
00101         self.mapwin.polypen = wx.Pen(colour = 'dark green', width = 2, style = wx.SHORT_DASH)
00102         self.mapwin.SetCursor(self.Parent.cursors["cross"])
00103 
00104     def OnSelectRaster(self, event):
00105         """!Select raster map(s) to profile
00106         """
00107         dlg = ProfileRasterDialog(parent = self)
00108 
00109         if dlg.ShowModal() == wx.ID_OK:
00110             self.rasterList = dlg.rasterList
00111             self.raster = self.InitRasterOpts(self.rasterList, self.plottype)
00112             
00113             # plot profile
00114             if len(self.mapwin.polycoords) > 0 and len(self.rasterList) > 0:
00115                 self.OnCreateProfile(event = None)
00116 
00117         dlg.Destroy()
00118 
00119     def SetupProfile(self):
00120         """!Create coordinate string for profiling. Create segment list for
00121            transect segment markers.
00122         """
00123 
00124         #
00125         # create list of coordinate points for r.profile
00126         #                
00127         dist = 0
00128         cumdist = 0
00129         self.coordstr = ''
00130         lasteast = lastnorth = None
00131         
00132         if len(self.mapwin.polycoords) > 0:
00133             for point in self.mapwin.polycoords:
00134                 # build string of coordinate points for r.profile
00135                 if self.coordstr == '':
00136                     self.coordstr = '%d,%d' % (point[0], point[1])
00137                 else:
00138                     self.coordstr = '%s,%d,%d' % (self.coordstr, point[0], point[1])
00139 
00140         if len(self.rasterList) == 0:
00141             return
00142 
00143         # title of window
00144         self.ptitle = _('Profile of')
00145 
00146         #
00147         # create list of coordinates for transect segment markers
00148         #
00149         if len(self.mapwin.polycoords) > 0:
00150             self.seglist = []
00151             for point in self.mapwin.polycoords:
00152                 # get value of raster cell at coordinate point
00153                 ret = RunCommand('r.what',
00154                                  parent = self,
00155                                  read = True,
00156                                  input = self.rasterList[0],
00157                                  east_north = '%d,%d' % (point[0],point[1]))
00158                 
00159                 val = ret.splitlines()[0].split('|')[3]
00160                 if val == None or val == '*': continue
00161                 val = float(val)
00162                 
00163                 # calculate distance between coordinate points
00164                 if lasteast and lastnorth:
00165                     dist = math.sqrt(math.pow((lasteast-point[0]),2) + math.pow((lastnorth-point[1]),2))
00166                 cumdist += dist
00167                 
00168                 #store total transect length
00169                 self.transect_length = cumdist
00170 
00171                 # build a list of distance,value pairs for each segment of transect
00172                 self.seglist.append((cumdist,val))
00173                 lasteast = point[0]
00174                 lastnorth = point[1]
00175 
00176             # delete extra first segment point
00177             try:
00178                 self.seglist.pop(0)
00179             except:
00180                 pass
00181 
00182         #
00183         # create datalist of dist/value pairs and y labels for each raster map
00184         #    
00185         self.ylabel = ''
00186         i = 0
00187         
00188         for r in self.raster.iterkeys():
00189             self.raster[r]['datalist'] = []
00190             datalist = self.CreateDatalist(r, self.coordstr)
00191             if len(datalist) > 0:   
00192                 self.raster[r]['datalist'] = datalist
00193 
00194                 # update ylabel to match units if they exist           
00195                 if self.raster[r]['units'] != '':
00196                     self.ylabel += '%s (%d),' % (r['units'], i)
00197                 i += 1
00198 
00199                 # update title
00200                 self.ptitle += ' %s ,' % r.split('@')[0]
00201 
00202         self.ptitle = self.ptitle.rstrip(',')
00203             
00204         if self.ylabel == '':
00205             self.ylabel = _('Raster values')
00206         else:
00207             self.ylabel = self.ylabel.rstrip(',')
00208 
00209     def CreateDatalist(self, raster, coords):
00210         """!Build a list of distance, value pairs for points along transect using r.profile
00211         """
00212         datalist = []
00213         
00214         # keep total number of transect points to 500 or less to avoid 
00215         # freezing with large, high resolution maps
00216         region = grass.region()
00217         curr_res = min(float(region['nsres']),float(region['ewres']))
00218         transect_rec = 0
00219         if self.transect_length / curr_res > 500:
00220             transect_res = self.transect_length / 500
00221         else: transect_res = curr_res
00222         
00223         ret = RunCommand("r.profile",
00224                          parent = self,
00225                          input = raster,
00226                          profile = coords,
00227                          res = transect_res,
00228                          null = "nan",
00229                          quiet = True,
00230                          read = True)
00231         
00232         if not ret:
00233             return []
00234             
00235         for line in ret.splitlines():
00236             dist, elev = line.strip().split(' ')
00237             if dist == None or dist == '' or dist == 'nan' or \
00238                 elev == None or elev == '' or elev == 'nan':
00239                 continue
00240             dist = float(dist)
00241             elev = float(elev)
00242             datalist.append((dist,elev))
00243 
00244         return datalist
00245 
00246     def OnCreateProfile(self, event):
00247         """!Main routine for creating a profile. Uses r.profile to
00248         create a list of distance,cell value pairs. This is passed to
00249         plot to create a line graph of the profile. If the profile
00250         transect is in multiple segments, these are drawn as
00251         points. Profile transect is drawn, using methods in mapdisp.py
00252         """
00253             
00254         if len(self.mapwin.polycoords) == 0 or len(self.rasterList) == 0:
00255             dlg = wx.MessageDialog(parent = self,
00256                                    message = _('You must draw a transect to profile in the map display window.'),
00257                                    caption = _('Nothing to profile'),
00258                                    style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
00259             dlg.ShowModal()
00260             dlg.Destroy()
00261             return
00262 
00263         self.mapwin.SetCursor(self.parent.cursors["default"])
00264         self.SetCursor(self.parent.cursors["default"])
00265         self.SetGraphStyle()
00266         self.SetupProfile()
00267         p = self.CreatePlotList()
00268         self.DrawPlot(p)
00269 
00270         # reset transect
00271         self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0,0.0)
00272         self.mapwin.mouse['use'] = 'pointer'
00273         self.mapwin.mouse['box'] = 'point'
00274 
00275     def CreatePlotList(self):
00276         """!Create a plot data list from transect datalist and
00277             transect segment endpoint coordinates.
00278         """
00279         # graph the distance, value pairs for the transect
00280         self.plotlist = []
00281 
00282         # Add segment marker points to plot data list
00283         if len(self.seglist) > 0 :
00284             self.ppoints = plot.PolyMarker(self.seglist,
00285                                            legend = ' ' + self.properties['marker']['legend'],
00286                                            colour = wx.Color(self.properties['marker']['color'][0],
00287                                                            self.properties['marker']['color'][1],
00288                                                            self.properties['marker']['color'][2],
00289                                                            255),
00290                                            size = self.properties['marker']['size'],
00291                                            fillstyle = self.ptfilldict[self.properties['marker']['fill']],
00292                                            marker = self.properties['marker']['type'])
00293             self.plotlist.append(self.ppoints)
00294 
00295         # Add profile distance/elevation pairs to plot data list for each raster profiled
00296         for r in self.rasterList:
00297             col = wx.Color(self.raster[r]['pcolor'][0],
00298                            self.raster[r]['pcolor'][1],
00299                            self.raster[r]['pcolor'][2],
00300                            255)
00301             self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
00302                                                     colour = col,
00303                                                     width = self.raster[r]['pwidth'],
00304                                                     style = self.linestyledict[self.raster[r]['pstyle']],
00305                                                     legend = self.raster[r]['plegend'])
00306 
00307             self.plotlist.append(self.raster[r]['pline'])
00308 
00309         if len(self.plotlist) > 0:        
00310             return self.plotlist
00311         else:
00312             return None
00313 
00314     def Update(self):
00315         """!Update profile after changing options
00316         """
00317         self.SetGraphStyle()
00318         p = self.CreatePlotList()
00319         self.DrawPlot(p)
00320 
00321     def SaveProfileToFile(self, event):
00322         """!Save r.profile data to a csv file
00323         """    
00324         wildcard = _("Comma separated value (*.csv)|*.csv")
00325         
00326         dlg = wx.FileDialog(parent = self,
00327                             message = _("Path and prefix (for raster name) to save profile values..."),
00328                             defaultDir = os.getcwd(), 
00329                             defaultFile = "",  wildcard = wildcard, style = wx.SAVE)
00330         if dlg.ShowModal() == wx.ID_OK:
00331             path = dlg.GetPath()
00332             
00333             for r in self.rasterList:
00334                 pfile = path+'_'+str(r['name'])+'.csv'
00335                 try:
00336                     file = open(pfile, "w")
00337                 except IOError:
00338                     wx.MessageBox(parent = self,
00339                                   message = _("Unable to open file <%s> for writing.") % pfile,
00340                                   caption = _("Error"), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
00341                     return False
00342                 for datapair in self.raster[r]['datalist']:
00343                     file.write('%d,%d\n' % (float(datapair[0]),float(datapair[1])))
00344                                         
00345                 file.close()
00346 
00347         dlg.Destroy()
00348         
00349     def OnStats(self, event):
00350         """!Displays regression information in messagebox
00351         """
00352         message = []
00353         title = _('Statistics for Profile(s)')
00354 
00355         for r in self.raster.iterkeys():
00356             try:
00357                 rast = r.split('@')[0] 
00358                 statstr = 'Profile of %s\n\n' % rast
00359 
00360                 iterable = (i[1] for i in self.raster[r]['datalist'])            
00361                 a = numpy.fromiter(iterable, numpy.float)
00362                 
00363                 statstr += 'n: %f\n' % a.size
00364                 statstr += 'minimum: %f\n' % numpy.amin(a)
00365                 statstr += 'maximum: %f\n' % numpy.amax(a)
00366                 statstr += 'range: %f\n' % numpy.ptp(a)
00367                 statstr += 'mean: %f\n' % numpy.mean(a)
00368                 statstr += 'standard deviation: %f\n' % numpy.std(a)
00369                 statstr += 'variance: %f\n' % numpy.var(a)
00370                 cv = numpy.std(a)/numpy.mean(a)
00371                 statstr += 'coefficient of variation: %f\n' % cv
00372                 statstr += 'sum: %f\n' % numpy.sum(a)
00373                 statstr += 'median: %f\n' % numpy.median(a)
00374                 statstr += 'distance along transect: %f\n\n' % self.transect_length       
00375                 message.append(statstr)
00376             except:
00377                 pass
00378             
00379         stats = PlotStatsFrame(self, id = wx.ID_ANY, message = message, 
00380                                title = title)
00381 
00382         if stats.Show() == wx.ID_CLOSE:
00383             stats.Destroy()       
00384 
00385     
00386 class ProfileToolbar(BaseToolbar):
00387     """!Toolbar for profiling raster map
00388     """ 
00389     def __init__(self, parent):
00390         BaseToolbar.__init__(self, parent)
00391         
00392         self.InitToolbar(self._toolbarData())
00393         
00394         # realize the toolbar
00395         self.Realize()
00396         
00397     def _toolbarData(self):
00398         """!Toolbar data"""
00399         return self._getToolbarData((('addraster', BaseIcons["addRast"],
00400                                       self.parent.OnSelectRaster),
00401                                      ('transect', PlotIcons["transect"],
00402                                       self.parent.OnDrawTransect),
00403                                      (None, ),
00404                                      ('draw', PlotIcons["draw"],
00405                                       self.parent.OnCreateProfile),
00406                                      ('erase', BaseIcons["erase"],
00407                                       self.parent.OnErase),
00408                                      ('drag', BaseIcons['pan'],
00409                                       self.parent.OnDrag),
00410                                      ('zoom', BaseIcons['zoomIn'],
00411                                       self.parent.OnZoom),
00412                                      ('unzoom', BaseIcons['zoomBack'],
00413                                       self.parent.OnRedraw),
00414                                      (None, ),
00415                                      ('statistics', PlotIcons['statistics'],
00416                                       self.parent.OnStats),
00417                                      ('datasave', PlotIcons["save"],
00418                                       self.parent.SaveProfileToFile),
00419                                      ('image', BaseIcons["saveFile"],
00420                                       self.parent.SaveToFile),
00421                                      ('print', BaseIcons["print"],
00422                                       self.parent.PrintMenu),
00423                                      (None, ),
00424                                      ('settings', PlotIcons["options"],
00425                                       self.parent.PlotOptionsMenu),
00426                                      ('quit', PlotIcons["quit"],
00427                                       self.parent.OnQuit),
00428                                      ))