|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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 ))