profile.py 17 KB

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