wxplot.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. """!
  2. @package wxplot.py
  3. Iinteractive plotting using PyPlot (wx.lib.plot.py).
  4. Classes:
  5. - AbstractPlotFrame
  6. - HistFrame
  7. - ProfileFrame
  8. (C) 2011 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Michael Barton, Arizona State University
  12. """
  13. import os
  14. import sys
  15. import math
  16. import wx
  17. import wx.lib.colourselect as csel
  18. import globalvar
  19. import gcmd
  20. from render import Map
  21. from toolbars import Histogram2Toolbar
  22. from toolbars import ProfileToolbar
  23. from preferences import globalSettings as UserSettings
  24. import wxplot_dialogs as dialogs
  25. from grass.script import core as grass
  26. from grass.script import raster as raster
  27. try:
  28. import numpy
  29. import wx.lib.plot as plot
  30. except ImportError:
  31. msg = _("This module requires the NumPy module, which could not be "
  32. "imported. It probably is not installed (it's not part of the "
  33. "standard Python distribution). See the Numeric Python site "
  34. "(http://numpy.scipy.org) for information on downloading source or "
  35. "binaries.")
  36. print >> sys.stderr, "histogram2.py: " + msg
  37. class AbstractPlotFrame(wx.Frame):
  38. """!Abstract PyPlot display frame class"""
  39. def __init__(self, parent = None, id = wx.ID_ANY, title='', size = (700, 300),
  40. style = wx.DEFAULT_FRAME_STYLE, rasterList = [], **kwargs):
  41. wx.Frame.__init__(self, parent, id, title, size = size, style = style, **kwargs)
  42. self.parent = parent # MapFrame
  43. self.mapwin = self.parent.MapWindow
  44. self.Map = Map() # instance of render.Map to be associated with display
  45. self.rasterList = rasterList #list of rasters to plot
  46. self.raster = {} # dictionary of raster maps and their plotting parameters
  47. self.plottype = ''
  48. self.pstyledict = { 'solid' : wx.SOLID,
  49. 'dot' : wx.DOT,
  50. 'long-dash' : wx.LONG_DASH,
  51. 'short-dash' : wx.SHORT_DASH,
  52. 'dot-dash' : wx.DOT_DASH }
  53. self.ptfilldict = { 'transparent' : wx.TRANSPARENT,
  54. 'solid' : wx.SOLID }
  55. #
  56. # Icon
  57. #
  58. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  59. #
  60. # Add statusbar
  61. #
  62. self.statusbar = self.CreateStatusBar(number = 2, style = 0)
  63. self.statusbar.SetStatusWidths([-2, -1])
  64. #
  65. # Define canvas and settings
  66. #
  67. #
  68. self.client = plot.PlotCanvas(self)
  69. #define the function for drawing pointLabels
  70. self.client.SetPointLabelFunc(self.DrawPointLabel)
  71. # Create mouse event for showing cursor coords in status bar
  72. self.client.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
  73. # Show closest point when enabled
  74. self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
  75. self.plotlist = [] # list of things to plot
  76. self.plot = None # plot draw object
  77. self.ptitle = "" # title of window
  78. self.xlabel = "" # default X-axis label
  79. self.ylabel = "" # default Y-axis label
  80. #
  81. # Bind various events
  82. #
  83. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  84. self.CentreOnScreen()
  85. self._createColorDict()
  86. def _createColorDict(self):
  87. """!Create color dictionary to return wx.Color tuples
  88. for assigning colors to images in imagery groups"""
  89. self.colorDict = {}
  90. for clr in grass.named_colors.iterkeys():
  91. if clr == 'white' or clr == 'black': continue
  92. r = grass.named_colors[clr][0] * 255
  93. g = grass.named_colors[clr][1] * 255
  94. b = grass.named_colors[clr][2] * 255
  95. self.colorDict[clr] = (r,g,b,255)
  96. def InitPlotOpts(self, plottype):
  97. """!Initialize options for entire plot
  98. """
  99. self.plottype = plottype # histogram, profile, or scatter
  100. self.properties = {} # plot properties
  101. self.properties['font'] = {}
  102. self.properties['font']['prop'] = UserSettings.Get(group = self.plottype, key = 'font')
  103. self.properties['font']['wxfont'] = wx.Font(11, wx.FONTFAMILY_SWISS,
  104. wx.FONTSTYLE_NORMAL,
  105. wx.FONTWEIGHT_NORMAL)
  106. if self.plottype != 'histogram':
  107. self.properties['marker'] = UserSettings.Get(group = self.plottype, key = 'marker')
  108. # changing color string to tuple for markers/points
  109. colstr = str(self.properties['marker']['color'])
  110. self.properties['marker']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
  111. self.properties['grid'] = UserSettings.Get(group = self.plottype, key = 'grid')
  112. colstr = str(self.properties['grid']['color']) # changing color string to tuple
  113. self.properties['grid']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
  114. self.properties['x-axis'] = {}
  115. self.properties['x-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'x-axis')
  116. self.properties['x-axis']['axis'] = None
  117. self.properties['y-axis'] = {}
  118. self.properties['y-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'y-axis')
  119. self.properties['y-axis']['axis'] = None
  120. self.properties['legend'] = UserSettings.Get(group = self.plottype, key = 'legend')
  121. self.zoom = False # zooming disabled
  122. self.drag = False # draging disabled
  123. self.client.SetShowScrollbars(True) # vertical and horizontal scrollbars
  124. # x and y axis set to normal (non-log)
  125. self.client.setLogScale((False, False))
  126. if self.properties['x-axis']['prop']['type']:
  127. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  128. else:
  129. self.client.SetXSpec('auto')
  130. if self.properties['y-axis']['prop']['type']:
  131. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  132. else:
  133. self.client.SetYSpec('auto')
  134. def InitRasterOpts(self, rasterList):
  135. """!Initialize or update raster dictionary for plotting
  136. """
  137. rdict = {} # initialize a dictionary
  138. for r in rasterList:
  139. idx = rasterList.index(r)
  140. try:
  141. ret = raster.raster_info(r)
  142. except:
  143. continue
  144. # if r.info cannot parse map, skip it
  145. # self.raster[r] = UserSettings.Get(group = 'plot', key = 'raster') # some default settings
  146. rdict[r] = {} # initialize sub-dictionaries for each raster in the list
  147. if ret['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  148. rdict[r]['units'] = ''
  149. else:
  150. self.raster[r]['units'] = ret['units']
  151. rdict[r]['plegend'] = r.split('@')[0]
  152. rdict[r]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
  153. rdict[r]['pline'] = None
  154. rdict[r]['datatype'] = ret['datatype']
  155. rdict[r]['pwidth'] = 1
  156. rdict[r]['pstyle'] = 'solid'
  157. if idx <= len(self.colorList):
  158. rdict[r]['pcolor'] = self.colorDict[self.colorList[idx]]
  159. else:
  160. r = randint(0, 255)
  161. b = randint(0, 255)
  162. g = randint(0, 255)
  163. rdict[r]['pcolor'] = ((r,g,b,255))
  164. return rdict
  165. def SetGraphStyle(self):
  166. """!Set plot and text options
  167. """
  168. self.client.SetFont(self.properties['font']['wxfont'])
  169. self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
  170. self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
  171. self.client.SetEnableZoom(self.zoom)
  172. self.client.SetEnableDrag(self.drag)
  173. #
  174. # axis settings
  175. #
  176. if self.properties['x-axis']['prop']['type'] == 'custom':
  177. self.client.SetXSpec('min')
  178. else:
  179. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  180. if self.properties['y-axis']['prop']['type'] == 'custom':
  181. self.client.SetYSpec('min')
  182. else:
  183. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  184. if self.properties['x-axis']['prop']['type'] == 'custom' and \
  185. self.properties['x-axis']['prop']['min'] < self.properties['x-axis']['prop']['max']:
  186. self.properties['x-axis']['axis'] = (self.properties['x-axis']['prop']['min'],
  187. self.properties['x-axis']['prop']['max'])
  188. else:
  189. self.properties['x-axis']['axis'] = None
  190. if self.properties['y-axis']['prop']['type'] == 'custom' and \
  191. self.properties['y-axis']['prop']['min'] < self.properties['y-axis']['prop']['max']:
  192. self.properties['y-axis']['axis'] = (self.properties['y-axis']['prop']['min'],
  193. self.properties['y-axis']['prop']['max'])
  194. else:
  195. self.properties['y-axis']['axis'] = None
  196. self.client.SetEnableGrid(self.properties['grid']['enabled'])
  197. self.client.SetGridColour(wx.Color(self.properties['grid']['color'][0],
  198. self.properties['grid']['color'][1],
  199. self.properties['grid']['color'][2],
  200. 255))
  201. self.client.SetFontSizeLegend(self.properties['font']['prop']['legendSize'])
  202. self.client.SetEnableLegend(self.properties['legend']['enabled'])
  203. if self.properties['x-axis']['prop']['log'] == True:
  204. self.properties['x-axis']['axis'] = None
  205. self.client.SetXSpec('min')
  206. if self.properties['y-axis']['prop']['log'] == True:
  207. self.properties['y-axis']['axis'] = None
  208. self.client.SetYSpec('min')
  209. self.client.setLogScale((self.properties['x-axis']['prop']['log'],
  210. self.properties['y-axis']['prop']['log']))
  211. def DrawPlot(self, plotlist):
  212. """!Draw line and point plot from list plot elements.
  213. """
  214. self.plot = plot.PlotGraphics(plotlist,
  215. self.ptitle,
  216. self.xlabel,
  217. self.ylabel)
  218. if self.properties['x-axis']['prop']['type'] == 'custom':
  219. self.client.SetXSpec('min')
  220. else:
  221. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  222. if self.properties['y-axis']['prop']['type'] == 'custom':
  223. self.client.SetYSpec('min')
  224. else:
  225. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  226. self.client.Draw(self.plot, self.properties['x-axis']['axis'],
  227. self.properties['y-axis']['axis'])
  228. def DrawPointLabel(self, dc, mDataDict):
  229. """!This is the fuction that defines how the pointLabels are
  230. plotted dc - DC that will be passed mDataDict - Dictionary
  231. of data that you want to use for the pointLabel
  232. As an example I have decided I want a box at the curve
  233. point with some text information about the curve plotted
  234. below. Any wxDC method can be used.
  235. """
  236. dc.SetPen(wx.Pen(wx.BLACK))
  237. dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
  238. sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
  239. dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point
  240. px,py = mDataDict["pointXY"]
  241. cNum = mDataDict["curveNum"]
  242. pntIn = mDataDict["pIndex"]
  243. legend = mDataDict["legend"]
  244. #make a string to display
  245. s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
  246. dc.DrawText(s, sx , sy+1)
  247. def OnZoom(self, event):
  248. """!Enable zooming and disable dragging
  249. """
  250. self.zoom = True
  251. self.drag = False
  252. self.client.SetEnableZoom(self.zoom)
  253. self.client.SetEnableDrag(self.drag)
  254. def OnDrag(self, event):
  255. """!Enable dragging and disable zooming
  256. """
  257. self.zoom = False
  258. self.drag = True
  259. self.client.SetEnableDrag(self.drag)
  260. self.client.SetEnableZoom(self.zoom)
  261. def OnRedraw(self, event):
  262. """!Redraw the plot window. Unzoom to original size
  263. """
  264. self.client.Reset()
  265. self.client.Redraw()
  266. def OnErase(self, event):
  267. """!Erase the plot window
  268. """
  269. self.client.Clear()
  270. self.mapwin.ClearLines(self.mapwin.pdc)
  271. self.mapwin.ClearLines(self.mapwin.pdcTmp)
  272. self.mapwin.polycoords = []
  273. self.mapwin.Refresh()
  274. def SaveToFile(self, event):
  275. """!Save plot to graphics file
  276. """
  277. self.client.SaveFile()
  278. def OnMouseLeftDown(self,event):
  279. self.SetStatusText(_("Left Mouse Down at Point: (%.4f, %.4f)") % \
  280. self.client._getXY(event))
  281. event.Skip() # allows plotCanvas OnMouseLeftDown to be called
  282. def OnMotion(self, event):
  283. """!Indicate when mouse is outside the plot area
  284. """
  285. if self.client.OnLeave(event): print 'out of area'
  286. #show closest point (when enbled)
  287. if self.client.GetEnablePointLabel() == True:
  288. #make up dict with info for the pointLabel
  289. #I've decided to mark the closest point on the closest curve
  290. dlst = self.client.GetClosetPoint( self.client._getXY(event), pointScaled = True)
  291. if dlst != []: #returns [] if none
  292. curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
  293. #make up dictionary to pass to my user function (see DrawPointLabel)
  294. mDataDict = {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
  295. "pointXY":pointXY, "scaledXY":scaledXY}
  296. #pass dict to update the pointLabel
  297. self.client.UpdatePointLabel(mDataDict)
  298. event.Skip() #go to next handler
  299. def PlotOptionsMenu(self, event):
  300. """!Popup menu for plot and text options
  301. """
  302. point = wx.GetMousePosition()
  303. popt = wx.Menu()
  304. # Add items to the menu
  305. settext = wx.MenuItem(popt, wx.ID_ANY, _('Text settings'))
  306. popt.AppendItem(settext)
  307. self.Bind(wx.EVT_MENU, self.PlotText, settext)
  308. setgrid = wx.MenuItem(popt, wx.ID_ANY, _('Plot settings'))
  309. popt.AppendItem(setgrid)
  310. self.Bind(wx.EVT_MENU, self.PlotOptions, setgrid)
  311. # Popup the menu. If an item is selected then its handler
  312. # will be called before PopupMenu returns.
  313. self.PopupMenu(popt)
  314. popt.Destroy()
  315. def NotFunctional(self):
  316. """!Creates a 'not functional' message dialog
  317. """
  318. dlg = wx.MessageDialog(parent = self,
  319. message = _('This feature is not yet functional'),
  320. caption = _('Under Construction'),
  321. style = wx.OK | wx.ICON_INFORMATION)
  322. dlg.ShowModal()
  323. dlg.Destroy()
  324. def OnPlotText(self, dlg):
  325. """!Custom text settings for histogram plot.
  326. """
  327. self.ptitle = dlg.ptitle
  328. self.xlabel = dlg.xlabel
  329. self.ylabel = dlg.ylabel
  330. dlg.UpdateSettings()
  331. self.client.SetFont(self.properties['font']['wxfont'])
  332. self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
  333. self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
  334. if self.plot:
  335. self.plot.setTitle(dlg.ptitle)
  336. self.plot.setXLabel(dlg.xlabel)
  337. self.plot.setYLabel(dlg.ylabel)
  338. self.OnRedraw(event = None)
  339. def PlotText(self, event):
  340. """!Set custom text values for profile title and axis labels.
  341. """
  342. dlg = dialogs.TextDialog(parent = self, id = wx.ID_ANY,
  343. plottype = self.plottype,
  344. title = _('Histogram text settings'))
  345. if dlg.ShowModal() == wx.ID_OK:
  346. self.OnPlotText(dlg)
  347. dlg.Destroy()
  348. def PlotOptions(self, event):
  349. """!Set various profile options, including: line width, color,
  350. style; marker size, color, fill, and style; grid and legend
  351. options. Calls OptDialog class.
  352. """
  353. dlg = dialogs.OptDialog(parent = self, id = wx.ID_ANY,
  354. plottype = self.plottype,
  355. title = _('Plot settings'))
  356. btnval = dlg.ShowModal()
  357. if btnval == wx.ID_SAVE:
  358. dlg.UpdateSettings()
  359. self.SetGraphStyle()
  360. dlg.Destroy()
  361. elif btnval == wx.ID_CANCEL:
  362. dlg.Destroy()
  363. def PrintMenu(self, event):
  364. """!Print options and output menu
  365. """
  366. point = wx.GetMousePosition()
  367. printmenu = wx.Menu()
  368. for title, handler in ((_("Page setup"), self.OnPageSetup),
  369. (_("Print preview"), self.OnPrintPreview),
  370. (_("Print display"), self.OnDoPrint)):
  371. item = wx.MenuItem(printmenu, wx.ID_ANY, title)
  372. printmenu.AppendItem(item)
  373. self.Bind(wx.EVT_MENU, handler, item)
  374. # Popup the menu. If an item is selected then its handler
  375. # will be called before PopupMenu returns.
  376. self.PopupMenu(printmenu)
  377. printmenu.Destroy()
  378. def OnPageSetup(self, event):
  379. self.client.PageSetup()
  380. def OnPrintPreview(self, event):
  381. self.client.PrintPreview()
  382. def OnDoPrint(self, event):
  383. self.client.Printout()
  384. def OnQuit(self, event):
  385. self.Close(True)
  386. def OnCloseWindow(self, event):
  387. """!Close plot window and clean up
  388. """
  389. try:
  390. self.mapwin.ClearLines()
  391. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0, 0.0)
  392. self.mapwin.mouse['use'] = 'pointer'
  393. self.mapwin.mouse['box'] = 'point'
  394. self.mapwin.polycoords = []
  395. self.mapwin.UpdateMap(render = False, renderVector = False)
  396. except:
  397. pass
  398. self.mapwin.SetCursor(self.Parent.cursors["default"])
  399. self.Destroy()
  400. class HistFrame(AbstractPlotFrame):
  401. def __init__(self, parent, id, pos, style, size,
  402. title = _("GRASS Histogramming Tool"), rasterList = []):
  403. """!Mainframe for displaying histogram of raster map. Uses wx.lib.plot.
  404. """
  405. AbstractPlotFrame.__init__(self, parent)
  406. self.toolbar = Histogram2Toolbar(parent = self)
  407. self.SetToolBar(self.toolbar)
  408. #
  409. # Init variables
  410. #
  411. self.rasterList = rasterList
  412. self.plottype = 'histogram'
  413. self.group = ''
  414. self.ptitle = _('Histogram of') # title of window
  415. self.xlabel = _("Raster cell values") # default X-axis label
  416. self.ylabel = _("Cell counts") # default Y-axis label
  417. self.maptype = 'raster' # default type of histogram to plot
  418. self.histtype = 'count'
  419. self.bins = 255
  420. self.colorList = ["blue", "green", "red", "yellow", "magenta", "cyan", \
  421. "aqua", "black", "grey", "orange", "brown", "purple", "violet", \
  422. "indigo"]
  423. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  424. self.InitRasterOpts(self.rasterList)
  425. self._initOpts()
  426. def _initOpts(self):
  427. """!Initialize plot options
  428. """
  429. self.InitPlotOpts('histogram')
  430. def OnCreateHist(self, event):
  431. """!Main routine for creating a histogram. Uses r.stats to
  432. create a list of cell value and count/percent/area pairs. This is passed to
  433. plot to create a line graph of the histogram.
  434. """
  435. self.SetCursor(self.parent.cursors["default"])
  436. self.SetGraphStyle()
  437. self.SetupHistogram()
  438. p = self.CreatePlotList()
  439. self.DrawPlot(p)
  440. def OnSelectRaster(self, event):
  441. """!Select raster map(s) to profile
  442. """
  443. dlg = dialogs.HistRasterDialog(parent = self)
  444. if dlg.ShowModal() == wx.ID_OK:
  445. self.rasterList = dlg.rasterList
  446. self.group = dlg.group
  447. self.bins = dlg.bins
  448. self.histtype = dlg.histtype
  449. self.maptype = dlg.maptype
  450. self.raster = self.InitRasterOpts(self.rasterList)
  451. # plot histogram
  452. if len(self.rasterList) > 0:
  453. self.OnCreateHist(event = None)
  454. self.SetupHistogram()
  455. p = self.CreatePlotList()
  456. self.DrawPlot(p)
  457. dlg.Destroy()
  458. def SetupHistogram(self):
  459. """!Build data list for ploting each raster
  460. """
  461. #
  462. # populate raster dictionary
  463. #
  464. if len(self.rasterList) == 0: return # nothing selected
  465. for r in self.rasterList:
  466. self.raster[r]['datalist'] = self.CreateDatalist(r)
  467. #
  468. # update title
  469. #
  470. if self.maptype == 'group':
  471. self.ptitle = _('Histogram of %s') % self.group.split('@')[0]
  472. else:
  473. self.ptitle = _('Histogram of %s') % self.rasterList[0].split('@')[0]
  474. #
  475. # set xlabel based on first raster map in list to be histogrammed
  476. #
  477. units = self.raster[self.rasterList[0]]['units']
  478. if units != '' and units != '(none)' and units != None:
  479. self.xlabel = _('Raster cell values %s') % units
  480. else:
  481. self.xlabel = _('Raster cell values')
  482. #
  483. # set ylabel from self.histtype
  484. #
  485. if self.histtype == 'count': self.ylabel = _('Cell counts')
  486. if self.histtype == 'percent': self.ylabel = _('Percent of total cells')
  487. if self.histtype == 'area': self.ylabel = _('Area')
  488. def CreateDatalist(self, raster):
  489. """!Build a list of cell value, frequency pairs for histogram
  490. frequency can be in cell counts, percents, or area
  491. """
  492. datalist = []
  493. if self.histtype == 'count': freqflag = 'cn'
  494. if self.histtype == 'percent': freqflag = 'pn'
  495. if self.histtype == 'area': freqflag = 'an'
  496. try:
  497. ret = gcmd.RunCommand("r.stats",
  498. parent = self,
  499. input = raster,
  500. flags = freqflag,
  501. nsteps = self.bins,
  502. fs = ',',
  503. quiet = True,
  504. read = True)
  505. if not ret:
  506. return datalist
  507. for line in ret.splitlines():
  508. cellval, histval = line.strip().split(',')
  509. histval = histval.strip()
  510. if self.raster[raster]['datatype'] != 'CELL':
  511. cellval = cellval.split('-')[0]
  512. if self.histtype == 'percent':
  513. histval = histval.rstrip('%')
  514. datalist.append((cellval,histval))
  515. return datalist
  516. except gcmd.GException, e:
  517. gcmd.GError(parent = self,
  518. message = e.value)
  519. return None
  520. def CreatePlotList(self):
  521. """!Make list of elements to plot
  522. """
  523. # graph the cell value, frequency pairs for the histogram
  524. self.plotlist = []
  525. for r in self.rasterList:
  526. if len(self.raster[r]['datalist']) > 0:
  527. col = wx.Color(self.raster[r]['pcolor'][0],
  528. self.raster[r]['pcolor'][1],
  529. self.raster[r]['pcolor'][2],
  530. 255)
  531. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  532. colour = col,
  533. width = self.raster[r]['pwidth'],
  534. style = self.pstyledict[self.raster[r]['pstyle']],
  535. legend = self.raster[r]['plegend'])
  536. self.plotlist.append(self.raster[r]['pline'])
  537. if len(self.plotlist) > 0:
  538. return self.plotlist
  539. else:
  540. return None
  541. def Update(self):
  542. """!Update histogram after changing options
  543. """
  544. self.SetGraphStyle()
  545. p = self.CreatePlotList()
  546. self.DrawPlot(p)
  547. class ProfileFrame(AbstractPlotFrame):
  548. """!Mainframe for displaying profile of one or more raster maps. Uses wx.lib.plot.
  549. """
  550. def __init__(self, parent, id, pos, style, size,
  551. title = _("GRASS Profile Analysis Tool"), rasterList = []):
  552. AbstractPlotFrame.__init__(self, parent)
  553. self.toolbar = ProfileToolbar(parent = self)
  554. self.SetToolBar(self.toolbar)
  555. #
  556. # Init variables
  557. #
  558. self.rasterList = rasterList
  559. self.plottype = 'profile'
  560. self.coordstr = '' # string of coordinates for r.profile
  561. self.seglist = [] # segment endpoint list
  562. self.ppoints = '' # segment endpoints data
  563. self.transect_length = 0.0 # total transect length
  564. self.ptitle = _('Profile of') # title of window
  565. self.raster = {}
  566. self.colorList = ["blue", "red", "green", "yellow", "magenta", "cyan", \
  567. "aqua", "black", "grey", "orange", "brown", "purple", "violet", \
  568. "indigo"]
  569. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  570. self.InitRasterOpts(self.rasterList)
  571. self._initOpts()
  572. # determine units (axis labels)
  573. if self.parent.Map.projinfo['units'] != '':
  574. self.xlabel = _('Distance (%s)') % self.parent.Map.projinfo['units']
  575. else:
  576. self.xlabel = _("Distance along transect")
  577. self.ylabel = _("Cell values")
  578. def _initOpts(self):
  579. """!Initialize plot options
  580. """
  581. self.InitPlotOpts('profile')
  582. def OnDrawTransect(self, event):
  583. """!Draws transect to profile in map display
  584. """
  585. self.mapwin.polycoords = []
  586. self.seglist = []
  587. self.mapwin.ClearLines(self.mapwin.pdc)
  588. self.ppoints = ''
  589. self.parent.SetFocus()
  590. self.parent.Raise()
  591. self.mapwin.mouse['use'] = 'profile'
  592. self.mapwin.mouse['box'] = 'line'
  593. self.mapwin.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
  594. self.mapwin.polypen = wx.Pen(colour = 'dark green', width = 2, style = wx.SHORT_DASH)
  595. self.mapwin.SetCursor(self.Parent.cursors["cross"])
  596. def OnSelectRaster(self, event):
  597. """!Select raster map(s) to profile
  598. """
  599. dlg = dialogs.ProfileRasterDialog(parent = self)
  600. if dlg.ShowModal() == wx.ID_OK:
  601. self.rasterList = dlg.rasterList
  602. self.raster = self.InitRasterOpts(self.rasterList)
  603. # plot profile
  604. if len(self.mapwin.polycoords) > 0 and len(self.rasterList) > 0:
  605. self.OnCreateProfile(event = None)
  606. dlg.Destroy()
  607. def SetupProfile(self):
  608. """!Create coordinate string for profiling. Create segment list for
  609. transect segment markers.
  610. """
  611. #
  612. # create list of coordinate points for r.profile
  613. #
  614. dist = 0
  615. cumdist = 0
  616. self.coordstr = ''
  617. lasteast = lastnorth = None
  618. if len(self.mapwin.polycoords) > 0:
  619. for point in self.mapwin.polycoords:
  620. # build string of coordinate points for r.profile
  621. if self.coordstr == '':
  622. self.coordstr = '%d,%d' % (point[0], point[1])
  623. else:
  624. self.coordstr = '%s,%d,%d' % (self.coordstr, point[0], point[1])
  625. if len(self.rasterList) == 0:
  626. return
  627. # title of window
  628. self.ptitle = _('Profile of')
  629. #
  630. # create list of coordinates for transect segment markers
  631. #
  632. if len(self.mapwin.polycoords) > 0:
  633. for point in self.mapwin.polycoords:
  634. # get value of raster cell at coordinate point
  635. ret = gcmd.RunCommand('r.what',
  636. parent = self,
  637. read = True,
  638. input = self.rasterList[0],
  639. east_north = '%d,%d' % (point[0],point[1]))
  640. val = ret.splitlines()[0].split('|')[3]
  641. # calculate distance between coordinate points
  642. if lasteast and lastnorth:
  643. dist = math.sqrt(math.pow((lasteast-point[0]),2) + math.pow((lastnorth-point[1]),2))
  644. cumdist += dist
  645. #store total transect length
  646. self.transect_length = cumdist
  647. # build a list of distance,value pairs for each segment of transect
  648. self.seglist.append((cumdist,val))
  649. lasteast = point[0]
  650. lastnorth = point[1]
  651. # delete first and last segment point
  652. try:
  653. self.seglist.pop(0)
  654. self.seglist.pop()
  655. except:
  656. pass
  657. #
  658. # create datalist for each raster map
  659. #
  660. for r in self.raster.iterkeys():
  661. self.raster[r]['datalist'] = []
  662. self.raster[r]['datalist'] = self.CreateDatalist(r, self.coordstr)
  663. # update title
  664. self.ptitle += ' %s ,' % r.split('@')[0]
  665. self.ptitle = self.ptitle.rstrip(',')
  666. #
  667. # set ylabel to match units if they exist
  668. #
  669. self.ylabel = ''
  670. i = 0
  671. for r in self.rasterList:
  672. if self.raster[r]['units'] != '':
  673. self.ylabel += '%s (%d),' % (r['units'], i)
  674. i += 1
  675. if self.ylabel == '':
  676. self.ylabel = _('Raster values')
  677. else:
  678. self.ylabel = self.ylabel.rstrip(',')
  679. def CreateDatalist(self, raster, coords):
  680. """!Build a list of distance, value pairs for points along transect using r.profile
  681. """
  682. datalist = []
  683. # keep total number of transect points to 500 or less to avoid
  684. # freezing with large, high resolution maps
  685. region = grass.region()
  686. curr_res = min(float(region['nsres']),float(region['ewres']))
  687. transect_rec = 0
  688. if self.transect_length / curr_res > 500:
  689. transect_res = self.transect_length / 500
  690. else: transect_res = curr_res
  691. ret = gcmd.RunCommand("r.profile",
  692. parent = self,
  693. input = raster,
  694. profile = coords,
  695. res = transect_res,
  696. null = "nan",
  697. quiet = True,
  698. read = True)
  699. if not ret:
  700. return []
  701. for line in ret.splitlines():
  702. dist, elev = line.strip().split(' ')
  703. if elev != 'nan':
  704. datalist.append((dist,elev))
  705. return datalist
  706. def OnCreateProfile(self, event):
  707. """!Main routine for creating a profile. Uses r.profile to
  708. create a list of distance,cell value pairs. This is passed to
  709. plot to create a line graph of the profile. If the profile
  710. transect is in multiple segments, these are drawn as
  711. points. Profile transect is drawn, using methods in mapdisp.py
  712. """
  713. if len(self.mapwin.polycoords) == 0 or len(self.rasterList) == 0:
  714. dlg = wx.MessageDialog(parent = self,
  715. message = _('You must draw a transect to profile in the map display window.'),
  716. caption = _('Nothing to profile'),
  717. style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  718. dlg.ShowModal()
  719. dlg.Destroy()
  720. return
  721. self.mapwin.SetCursor(self.parent.cursors["default"])
  722. self.SetCursor(self.parent.cursors["default"])
  723. self.SetGraphStyle()
  724. self.SetupProfile()
  725. p = self.CreatePlotList()
  726. self.DrawPlot(p)
  727. # reset transect
  728. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0,0.0)
  729. self.mapwin.mouse['use'] = 'pointer'
  730. self.mapwin.mouse['box'] = 'point'
  731. def CreatePlotList(self):
  732. """!Create a plot data list from transect datalist and
  733. transect segment endpoint coordinates.
  734. """
  735. # graph the distance, value pairs for the transect
  736. self.plotlist = []
  737. # Add segment marker points to plot data list
  738. if len(self.seglist) > 0 :
  739. self.ppoints = plot.PolyMarker(self.seglist,
  740. legend = ' ' + self.properties['marker']['legend'],
  741. colour = wx.Color(self.properties['marker']['color'][0],
  742. self.properties['marker']['color'][1],
  743. self.properties['marker']['color'][2],
  744. 255),
  745. size = self.properties['marker']['size'],
  746. fillstyle = self.ptfilldict[self.properties['marker']['fill']],
  747. marker = self.properties['marker']['type'])
  748. self.plotlist.append(self.ppoints)
  749. # Add profile distance/elevation pairs to plot data list for each raster profiled
  750. for r in self.rasterList:
  751. col = wx.Color(self.raster[r]['pcolor'][0],
  752. self.raster[r]['pcolor'][1],
  753. self.raster[r]['pcolor'][2],
  754. 255)
  755. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  756. colour = col,
  757. width = self.raster[r]['pwidth'],
  758. style = self.pstyledict[self.raster[r]['pstyle']],
  759. legend = self.raster[r]['plegend'])
  760. self.plotlist.append(self.raster[r]['pline'])
  761. if len(self.plotlist) > 0:
  762. return self.plotlist
  763. else:
  764. return None
  765. def Update(self):
  766. """!Update profile after changing options
  767. """
  768. self.SetGraphStyle()
  769. p = self.CreatePlotList()
  770. self.DrawPlot(p)
  771. def SaveProfileToFile(self, event):
  772. """!Save r.profile data to a csv file
  773. """
  774. wildcard = _("Comma separated value (*.csv)|*.csv")
  775. dlg = wx.FileDialog(parent = self,
  776. message = _("Path and prefix (for raster name) to save profile values..."),
  777. defaultDir = os.getcwd(),
  778. defaultFile = "", wildcard = wildcard, style = wx.SAVE)
  779. if dlg.ShowModal() == wx.ID_OK:
  780. path = dlg.GetPath()
  781. for r in self.rasterList:
  782. pfile = path+'_'+str(r['name'])+'.csv'
  783. try:
  784. file = open(pfile, "w")
  785. except IOError:
  786. wx.MessageBox(parent = self,
  787. message = _("Unable to open file <%s> for writing.") % pfile,
  788. caption = _("Error"), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  789. return False
  790. for datapair in self.raster[r]['datalist']:
  791. file.write('%d,%d\n' % (float(datapair[0]),float(datapair[1])))
  792. file.close()
  793. dlg.Destroy()