profile.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. """!
  2. @package wxplot.profile
  3. @brief Profiling using PyPlot
  4. Classes:
  5. - profile::ProfileFrame
  6. - profile::ProfileToolbar
  7. (C) 2011 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Michael Barton, Arizona State University
  11. """
  12. import os
  13. import sys
  14. import math
  15. import wx
  16. import wx.lib.plot as plot
  17. from core.gcmd import GWarning
  18. import grass.script as grass
  19. try:
  20. import numpy
  21. except ImportError:
  22. msg = _("This module requires the NumPy module, which could not be "
  23. "imported. It probably is not installed (it's not part of the "
  24. "standard Python distribution). See the Numeric Python site "
  25. "(http://numpy.scipy.org) for information on downloading source or "
  26. "binaries.")
  27. print >> sys.stderr, "wxplot.py: " + msg
  28. from wxplot.base import BasePlotFrame, PlotIcons
  29. from gui_core.toolbars import BaseToolbar, BaseIcons
  30. from wxplot.dialogs import ProfileRasterDialog, PlotStatsFrame
  31. from core.gcmd import RunCommand
  32. class ProfileFrame(BasePlotFrame):
  33. """!Mainframe for displaying profile of one or more raster maps. Uses wx.lib.plot.
  34. """
  35. def __init__(self, parent, id = wx.ID_ANY, style = wx.DEFAULT_FRAME_STYLE,
  36. size = wx.Size(700, 400),
  37. rasterList = [], **kwargs):
  38. BasePlotFrame.__init__(self, parent, size = size, **kwargs)
  39. self.toolbar = ProfileToolbar(parent = self)
  40. self.SetToolBar(self.toolbar)
  41. self.SetTitle(_("GRASS Profile Analysis Tool"))
  42. #
  43. # Init variables
  44. #
  45. self.rasterList = rasterList
  46. self.plottype = 'profile'
  47. self.coordstr = '' # string of coordinates for r.profile
  48. self.seglist = [] # segment endpoint list
  49. self.ppoints = '' # segment endpoints data
  50. self.transect_length = 0.0 # total transect length
  51. self.ptitle = _('Profile of') # title of window
  52. self.colorList = ["blue", "red", "green", "yellow", "magenta", "cyan",
  53. "aqua", "black", "grey", "orange", "brown", "purple", "violet",
  54. "indigo"]
  55. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  56. self.raster = self.InitRasterOpts(self.rasterList, self.plottype)
  57. else:
  58. self.raster = {}
  59. self._initOpts()
  60. # determine units (axis labels)
  61. if self.parent.Map.projinfo['units'] != '':
  62. self.xlabel = _('Distance (%s)') % self.parent.Map.projinfo['units']
  63. else:
  64. self.xlabel = _("Distance along transect")
  65. self.ylabel = _("Cell values")
  66. def _initOpts(self):
  67. """!Initialize plot options
  68. """
  69. self.InitPlotOpts('profile')
  70. def OnDrawTransect(self, event):
  71. """!Draws transect to profile in map display
  72. """
  73. self.parent.SwitchTool(self.parent.toolbars['map'], event)
  74. self.mapwin.polycoords = []
  75. self.seglist = []
  76. self.mapwin.ClearLines(self.mapwin.pdc)
  77. self.ppoints = ''
  78. self.parent.SetFocus()
  79. self.parent.Raise()
  80. self.mapwin.mouse['use'] = 'profile'
  81. self.mapwin.mouse['box'] = 'line'
  82. self.mapwin.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
  83. self.mapwin.polypen = wx.Pen(colour = 'dark green', width = 2, style = wx.SHORT_DASH)
  84. self.mapwin.SetCursor(self.Parent.cursors["cross"])
  85. def OnSelectRaster(self, event):
  86. """!Select raster map(s) to profile
  87. """
  88. dlg = ProfileRasterDialog(parent = self)
  89. if dlg.ShowModal() == wx.ID_OK:
  90. self.rasterList = dlg.rasterList
  91. self.raster = self.InitRasterOpts(self.rasterList, self.plottype)
  92. # plot profile
  93. if len(self.mapwin.polycoords) > 0 and len(self.rasterList) > 0:
  94. self.OnCreateProfile(event = None)
  95. dlg.Destroy()
  96. def SetupProfile(self):
  97. """!Create coordinate string for profiling. Create segment
  98. list for transect segment markers.
  99. """
  100. # create list of coordinate points for r.profile
  101. dist = 0
  102. cumdist = 0
  103. self.coordstr = ''
  104. lasteast = lastnorth = None
  105. region = grass.region()
  106. insideRegion = True
  107. if len(self.mapwin.polycoords) > 0:
  108. for point in self.mapwin.polycoords:
  109. if not (region['w'] <= point[0] <= region['e'] and region['s'] <= point[1] <= region['n']):
  110. insideRegion = False
  111. # build string of coordinate points for r.profile
  112. if self.coordstr == '':
  113. self.coordstr = '%d,%d' % (point[0], point[1])
  114. else:
  115. self.coordstr = '%s,%d,%d' % (self.coordstr, point[0], point[1])
  116. if not insideRegion:
  117. GWarning(message = _("Not all points of profile lie inside computational region."),
  118. parent = self)
  119. if len(self.rasterList) == 0:
  120. return
  121. # title of window
  122. self.ptitle = _('Profile of')
  123. # create list of coordinates for transect segment markers
  124. if len(self.mapwin.polycoords) > 0:
  125. self.seglist = []
  126. for point in self.mapwin.polycoords:
  127. # get value of raster cell at coordinate point
  128. ret = RunCommand('r.what',
  129. parent = self,
  130. read = True,
  131. map = self.rasterList[0],
  132. coordinates = '%d,%d' % (point[0],point[1]))
  133. val = ret.splitlines()[0].split('|')[3]
  134. if val == None or val == '*':
  135. continue
  136. val = float(val)
  137. # calculate distance between coordinate points
  138. if lasteast and lastnorth:
  139. dist = math.sqrt(math.pow((lasteast-point[0]),2) + math.pow((lastnorth-point[1]),2))
  140. cumdist += dist
  141. # store total transect length
  142. self.transect_length = cumdist
  143. # build a list of distance,value pairs for each segment of transect
  144. self.seglist.append((cumdist,val))
  145. lasteast = point[0]
  146. lastnorth = point[1]
  147. # delete extra first segment point
  148. try:
  149. self.seglist.pop(0)
  150. except:
  151. pass
  152. #
  153. # create datalist of dist/value pairs and y labels for each raster map
  154. #
  155. self.ylabel = ''
  156. i = 0
  157. for r in self.raster.iterkeys():
  158. self.raster[r]['datalist'] = []
  159. datalist = self.CreateDatalist(r, self.coordstr)
  160. if len(datalist) > 0:
  161. self.raster[r]['datalist'] = datalist
  162. # update ylabel to match units if they exist
  163. if self.raster[r]['units'] != '':
  164. self.ylabel += '%s (%d),' % (r['units'], i)
  165. i += 1
  166. # update title
  167. self.ptitle += ' %s ,' % r.split('@')[0]
  168. self.ptitle = self.ptitle.rstrip(',')
  169. if self.ylabel == '':
  170. self.ylabel = _('Raster values')
  171. else:
  172. self.ylabel = self.ylabel.rstrip(',')
  173. def CreateDatalist(self, raster, coords):
  174. """!Build a list of distance, value pairs for points along transect using r.profile
  175. """
  176. datalist = []
  177. # keep total number of transect points to 500 or less to avoid
  178. # freezing with large, high resolution maps
  179. region = grass.region()
  180. curr_res = min(float(region['nsres']),float(region['ewres']))
  181. transect_rec = 0
  182. if self.transect_length / curr_res > 500:
  183. transect_res = self.transect_length / 500
  184. else: transect_res = curr_res
  185. ret = RunCommand("r.profile",
  186. parent = self,
  187. input = raster,
  188. profile = coords,
  189. res = transect_res,
  190. null = "nan",
  191. quiet = True,
  192. read = True)
  193. if not ret:
  194. return []
  195. for line in ret.splitlines():
  196. dist, elev = line.strip().split(' ')
  197. if dist == None or dist == '' or dist == 'nan' or \
  198. elev == None or elev == '' or elev == 'nan':
  199. continue
  200. dist = float(dist)
  201. elev = float(elev)
  202. datalist.append((dist,elev))
  203. return datalist
  204. def OnCreateProfile(self, event):
  205. """!Main routine for creating a profile. Uses r.profile to
  206. create a list of distance,cell value pairs. This is passed to
  207. plot to create a line graph of the profile. If the profile
  208. transect is in multiple segments, these are drawn as
  209. points. Profile transect is drawn, using methods in mapdisp.py
  210. """
  211. if len(self.mapwin.polycoords) == 0 or len(self.rasterList) == 0:
  212. dlg = wx.MessageDialog(parent = self,
  213. message = _('You must draw a transect to profile in the map display window.'),
  214. caption = _('Nothing to profile'),
  215. style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  216. dlg.ShowModal()
  217. dlg.Destroy()
  218. return
  219. self.mapwin.SetCursor(self.parent.cursors["default"])
  220. self.SetCursor(self.parent.cursors["default"])
  221. self.SetGraphStyle()
  222. self.SetupProfile()
  223. p = self.CreatePlotList()
  224. self.DrawPlot(p)
  225. # reset transect
  226. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0,0.0)
  227. self.mapwin.mouse['use'] = 'pointer'
  228. self.mapwin.mouse['box'] = 'point'
  229. def CreatePlotList(self):
  230. """!Create a plot data list from transect datalist and
  231. transect segment endpoint coordinates.
  232. """
  233. # graph the distance, value pairs for the transect
  234. self.plotlist = []
  235. # Add segment marker points to plot data list
  236. if len(self.seglist) > 0 :
  237. self.ppoints = plot.PolyMarker(self.seglist,
  238. legend = ' ' + self.properties['marker']['legend'],
  239. colour = wx.Color(self.properties['marker']['color'][0],
  240. self.properties['marker']['color'][1],
  241. self.properties['marker']['color'][2],
  242. 255),
  243. size = self.properties['marker']['size'],
  244. fillstyle = self.ptfilldict[self.properties['marker']['fill']],
  245. marker = self.properties['marker']['type'])
  246. self.plotlist.append(self.ppoints)
  247. # Add profile distance/elevation pairs to plot data list for each raster profiled
  248. for r in self.rasterList:
  249. col = wx.Color(self.raster[r]['pcolor'][0],
  250. self.raster[r]['pcolor'][1],
  251. self.raster[r]['pcolor'][2],
  252. 255)
  253. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  254. colour = col,
  255. width = self.raster[r]['pwidth'],
  256. style = self.linestyledict[self.raster[r]['pstyle']],
  257. legend = self.raster[r]['plegend'])
  258. self.plotlist.append(self.raster[r]['pline'])
  259. if len(self.plotlist) > 0:
  260. return self.plotlist
  261. else:
  262. return None
  263. def Update(self):
  264. """!Update profile after changing options
  265. """
  266. self.SetGraphStyle()
  267. p = self.CreatePlotList()
  268. self.DrawPlot(p)
  269. def SaveProfileToFile(self, event):
  270. """!Save r.profile data to a csv file
  271. """
  272. wildcard = _("Comma separated value (*.csv)|*.csv")
  273. dlg = wx.FileDialog(parent = self,
  274. message = _("Path and prefix (for raster name) to save profile values..."),
  275. defaultDir = os.getcwd(),
  276. defaultFile = "", wildcard = wildcard, style = wx.SAVE)
  277. if dlg.ShowModal() == wx.ID_OK:
  278. path = dlg.GetPath()
  279. for r in self.rasterList:
  280. pfile = path+'_'+str(r['name'])+'.csv'
  281. try:
  282. file = open(pfile, "w")
  283. except IOError:
  284. wx.MessageBox(parent = self,
  285. message = _("Unable to open file <%s> for writing.") % pfile,
  286. caption = _("Error"), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  287. return False
  288. for datapair in self.raster[r]['datalist']:
  289. file.write('%d,%d\n' % (float(datapair[0]),float(datapair[1])))
  290. file.close()
  291. dlg.Destroy()
  292. def OnStats(self, event):
  293. """!Displays regression information in messagebox
  294. """
  295. message = []
  296. title = _('Statistics for Profile(s)')
  297. for r in self.raster.iterkeys():
  298. try:
  299. rast = r.split('@')[0]
  300. statstr = 'Profile of %s\n\n' % rast
  301. iterable = (i[1] for i in self.raster[r]['datalist'])
  302. a = numpy.fromiter(iterable, numpy.float)
  303. statstr += 'n: %f\n' % a.size
  304. statstr += 'minimum: %f\n' % numpy.amin(a)
  305. statstr += 'maximum: %f\n' % numpy.amax(a)
  306. statstr += 'range: %f\n' % numpy.ptp(a)
  307. statstr += 'mean: %f\n' % numpy.mean(a)
  308. statstr += 'standard deviation: %f\n' % numpy.std(a)
  309. statstr += 'variance: %f\n' % numpy.var(a)
  310. cv = numpy.std(a)/numpy.mean(a)
  311. statstr += 'coefficient of variation: %f\n' % cv
  312. statstr += 'sum: %f\n' % numpy.sum(a)
  313. statstr += 'median: %f\n' % numpy.median(a)
  314. statstr += 'distance along transect: %f\n\n' % self.transect_length
  315. message.append(statstr)
  316. except:
  317. pass
  318. stats = PlotStatsFrame(self, id = wx.ID_ANY, message = message,
  319. title = title)
  320. if stats.Show() == wx.ID_CLOSE:
  321. stats.Destroy()
  322. class ProfileToolbar(BaseToolbar):
  323. """!Toolbar for profiling raster map
  324. """
  325. def __init__(self, parent):
  326. BaseToolbar.__init__(self, parent)
  327. self.InitToolbar(self._toolbarData())
  328. # realize the toolbar
  329. self.Realize()
  330. def _toolbarData(self):
  331. """!Toolbar data"""
  332. return self._getToolbarData((('addraster', BaseIcons["addRast"],
  333. self.parent.OnSelectRaster),
  334. ('transect', PlotIcons["transect"],
  335. self.parent.OnDrawTransect),
  336. (None, ),
  337. ('draw', PlotIcons["draw"],
  338. self.parent.OnCreateProfile),
  339. ('erase', BaseIcons["erase"],
  340. self.parent.OnErase),
  341. ('drag', BaseIcons['pan'],
  342. self.parent.OnDrag),
  343. ('zoom', BaseIcons['zoomIn'],
  344. self.parent.OnZoom),
  345. ('unzoom', BaseIcons['zoomBack'],
  346. self.parent.OnRedraw),
  347. (None, ),
  348. ('statistics', PlotIcons['statistics'],
  349. self.parent.OnStats),
  350. ('datasave', PlotIcons["save"],
  351. self.parent.SaveProfileToFile),
  352. ('image', BaseIcons["saveFile"],
  353. self.parent.SaveToFile),
  354. ('print', BaseIcons["print"],
  355. self.parent.PrintMenu),
  356. (None, ),
  357. ('settings', PlotIcons["options"],
  358. self.parent.PlotOptionsMenu),
  359. ('quit', PlotIcons["quit"],
  360. self.parent.OnQuit),
  361. ))