wxplot.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  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, id = wx.ID_ANY, title = '', size = (700, 400),
  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, title = _("GRASS Histogramming Tool"),
  402. rasterList = [], **kwargs):
  403. """!Mainframe for displaying histogram of raster map. Uses wx.lib.plot.
  404. """
  405. AbstractPlotFrame.__init__(self, parent, title = title,
  406. rasterList = rasterList, **kwargs)
  407. self.toolbar = Histogram2Toolbar(parent = self)
  408. self.SetToolBar(self.toolbar)
  409. #
  410. # Init variables
  411. #
  412. self.rasterList = rasterList
  413. self.plottype = 'histogram'
  414. self.group = ''
  415. self.ptitle = _('Histogram of') # title of window
  416. self.xlabel = _("Raster cell values") # default X-axis label
  417. self.ylabel = _("Cell counts") # default Y-axis label
  418. self.maptype = 'raster' # default type of histogram to plot
  419. self.histtype = 'count'
  420. self.bins = 255
  421. self.colorList = ["blue", "green", "red", "yellow", "magenta", "cyan", \
  422. "aqua", "black", "grey", "orange", "brown", "purple", "violet", \
  423. "indigo"]
  424. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  425. self.InitRasterOpts(self.rasterList)
  426. self._initOpts()
  427. def _initOpts(self):
  428. """!Initialize plot options
  429. """
  430. self.InitPlotOpts('histogram')
  431. def OnCreateHist(self, event):
  432. """!Main routine for creating a histogram. Uses r.stats to
  433. create a list of cell value and count/percent/area pairs. This is passed to
  434. plot to create a line graph of the histogram.
  435. """
  436. self.SetCursor(self.parent.cursors["default"])
  437. self.SetGraphStyle()
  438. self.SetupHistogram()
  439. p = self.CreatePlotList()
  440. self.DrawPlot(p)
  441. def OnSelectRaster(self, event):
  442. """!Select raster map(s) to profile
  443. """
  444. dlg = dialogs.HistRasterDialog(parent = self)
  445. if dlg.ShowModal() == wx.ID_OK:
  446. self.rasterList = dlg.rasterList
  447. self.group = dlg.group
  448. self.bins = dlg.bins
  449. self.histtype = dlg.histtype
  450. self.maptype = dlg.maptype
  451. self.raster = self.InitRasterOpts(self.rasterList)
  452. # plot histogram
  453. if len(self.rasterList) > 0:
  454. self.OnCreateHist(event = None)
  455. self.SetupHistogram()
  456. p = self.CreatePlotList()
  457. self.DrawPlot(p)
  458. dlg.Destroy()
  459. def SetupHistogram(self):
  460. """!Build data list for ploting each raster
  461. """
  462. #
  463. # populate raster dictionary
  464. #
  465. if len(self.rasterList) == 0: return # nothing selected
  466. for r in self.rasterList:
  467. self.raster[r]['datalist'] = self.CreateDatalist(r)
  468. #
  469. # update title
  470. #
  471. if self.maptype == 'group':
  472. self.ptitle = _('Histogram of %s') % self.group.split('@')[0]
  473. else:
  474. self.ptitle = _('Histogram of %s') % self.rasterList[0].split('@')[0]
  475. #
  476. # set xlabel based on first raster map in list to be histogrammed
  477. #
  478. units = self.raster[self.rasterList[0]]['units']
  479. if units != '' and units != '(none)' and units != None:
  480. self.xlabel = _('Raster cell values %s') % units
  481. else:
  482. self.xlabel = _('Raster cell values')
  483. #
  484. # set ylabel from self.histtype
  485. #
  486. if self.histtype == 'count': self.ylabel = _('Cell counts')
  487. if self.histtype == 'percent': self.ylabel = _('Percent of total cells')
  488. if self.histtype == 'area': self.ylabel = _('Area')
  489. def CreateDatalist(self, raster):
  490. """!Build a list of cell value, frequency pairs for histogram
  491. frequency can be in cell counts, percents, or area
  492. """
  493. datalist = []
  494. if self.histtype == 'count': freqflag = 'cn'
  495. if self.histtype == 'percent': freqflag = 'pn'
  496. if self.histtype == 'area': freqflag = 'an'
  497. try:
  498. ret = gcmd.RunCommand("r.stats",
  499. parent = self,
  500. input = raster,
  501. flags = freqflag,
  502. nsteps = self.bins,
  503. fs = ',',
  504. quiet = True,
  505. read = True)
  506. if not ret:
  507. return datalist
  508. for line in ret.splitlines():
  509. cellval, histval = line.strip().split(',')
  510. histval = histval.strip()
  511. if self.raster[raster]['datatype'] != 'CELL':
  512. cellval = cellval.split('-')[0]
  513. if self.histtype == 'percent':
  514. histval = histval.rstrip('%')
  515. datalist.append((cellval,histval))
  516. return datalist
  517. except gcmd.GException, e:
  518. gcmd.GError(parent = self,
  519. message = e.value)
  520. return None
  521. def CreatePlotList(self):
  522. """!Make list of elements to plot
  523. """
  524. # graph the cell value, frequency pairs for the histogram
  525. self.plotlist = []
  526. for r in self.rasterList:
  527. if len(self.raster[r]['datalist']) > 0:
  528. col = wx.Color(self.raster[r]['pcolor'][0],
  529. self.raster[r]['pcolor'][1],
  530. self.raster[r]['pcolor'][2],
  531. 255)
  532. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  533. colour = col,
  534. width = self.raster[r]['pwidth'],
  535. style = self.pstyledict[self.raster[r]['pstyle']],
  536. legend = self.raster[r]['plegend'])
  537. self.plotlist.append(self.raster[r]['pline'])
  538. if len(self.plotlist) > 0:
  539. return self.plotlist
  540. else:
  541. return None
  542. def Update(self):
  543. """!Update histogram after changing options
  544. """
  545. self.SetGraphStyle()
  546. p = self.CreatePlotList()
  547. self.DrawPlot(p)
  548. class ProfileFrame(AbstractPlotFrame):
  549. """!Mainframe for displaying profile of one or more raster maps. Uses wx.lib.plot.
  550. """
  551. def __init__(self, parent, title = _("GRASS Profile Analysis Tool"),
  552. rasterList = [], **kwargs):
  553. AbstractPlotFrame.__init__(self, parent, title = title,
  554. rasterList = rasterList, **kwargs)
  555. self.toolbar = ProfileToolbar(parent = self)
  556. self.SetToolBar(self.toolbar)
  557. #
  558. # Init variables
  559. #
  560. self.rasterList = rasterList
  561. self.plottype = 'profile'
  562. self.coordstr = '' # string of coordinates for r.profile
  563. self.seglist = [] # segment endpoint list
  564. self.ppoints = '' # segment endpoints data
  565. self.transect_length = 0.0 # total transect length
  566. self.ptitle = _('Profile of') # title of window
  567. self.raster = {}
  568. self.colorList = ["blue", "red", "green", "yellow", "magenta", "cyan", \
  569. "aqua", "black", "grey", "orange", "brown", "purple", "violet", \
  570. "indigo"]
  571. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  572. self.InitRasterOpts(self.rasterList)
  573. self._initOpts()
  574. # determine units (axis labels)
  575. if self.parent.Map.projinfo['units'] != '':
  576. self.xlabel = _('Distance (%s)') % self.parent.Map.projinfo['units']
  577. else:
  578. self.xlabel = _("Distance along transect")
  579. self.ylabel = _("Cell values")
  580. def _initOpts(self):
  581. """!Initialize plot options
  582. """
  583. self.InitPlotOpts('profile')
  584. def OnDrawTransect(self, event):
  585. """!Draws transect to profile in map display
  586. """
  587. self.mapwin.polycoords = []
  588. self.seglist = []
  589. self.mapwin.ClearLines(self.mapwin.pdc)
  590. self.ppoints = ''
  591. self.parent.SetFocus()
  592. self.parent.Raise()
  593. self.mapwin.mouse['use'] = 'profile'
  594. self.mapwin.mouse['box'] = 'line'
  595. self.mapwin.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
  596. self.mapwin.polypen = wx.Pen(colour = 'dark green', width = 2, style = wx.SHORT_DASH)
  597. self.mapwin.SetCursor(self.Parent.cursors["cross"])
  598. def OnSelectRaster(self, event):
  599. """!Select raster map(s) to profile
  600. """
  601. dlg = dialogs.ProfileRasterDialog(parent = self)
  602. if dlg.ShowModal() == wx.ID_OK:
  603. self.rasterList = dlg.rasterList
  604. self.raster = self.InitRasterOpts(self.rasterList)
  605. # plot profile
  606. if len(self.mapwin.polycoords) > 0 and len(self.rasterList) > 0:
  607. self.OnCreateProfile(event = None)
  608. dlg.Destroy()
  609. def SetupProfile(self):
  610. """!Create coordinate string for profiling. Create segment list for
  611. transect segment markers.
  612. """
  613. #
  614. # create list of coordinate points for r.profile
  615. #
  616. dist = 0
  617. cumdist = 0
  618. self.coordstr = ''
  619. lasteast = lastnorth = None
  620. if len(self.mapwin.polycoords) > 0:
  621. for point in self.mapwin.polycoords:
  622. # build string of coordinate points for r.profile
  623. if self.coordstr == '':
  624. self.coordstr = '%d,%d' % (point[0], point[1])
  625. else:
  626. self.coordstr = '%s,%d,%d' % (self.coordstr, point[0], point[1])
  627. if len(self.rasterList) == 0:
  628. return
  629. # title of window
  630. self.ptitle = _('Profile of')
  631. #
  632. # create list of coordinates for transect segment markers
  633. #
  634. if len(self.mapwin.polycoords) > 0:
  635. for point in self.mapwin.polycoords:
  636. # get value of raster cell at coordinate point
  637. ret = gcmd.RunCommand('r.what',
  638. parent = self,
  639. read = True,
  640. input = self.rasterList[0],
  641. east_north = '%d,%d' % (point[0],point[1]))
  642. val = ret.splitlines()[0].split('|')[3]
  643. # calculate distance between coordinate points
  644. if lasteast and lastnorth:
  645. dist = math.sqrt(math.pow((lasteast-point[0]),2) + math.pow((lastnorth-point[1]),2))
  646. cumdist += dist
  647. #store total transect length
  648. self.transect_length = cumdist
  649. # build a list of distance,value pairs for each segment of transect
  650. self.seglist.append((cumdist,val))
  651. lasteast = point[0]
  652. lastnorth = point[1]
  653. # delete first and last segment point
  654. try:
  655. self.seglist.pop(0)
  656. self.seglist.pop()
  657. except:
  658. pass
  659. #
  660. # create datalist for each raster map
  661. #
  662. for r in self.raster.iterkeys():
  663. self.raster[r]['datalist'] = []
  664. self.raster[r]['datalist'] = self.CreateDatalist(r, self.coordstr)
  665. # update title
  666. self.ptitle += ' %s ,' % r.split('@')[0]
  667. self.ptitle = self.ptitle.rstrip(',')
  668. #
  669. # set ylabel to match units if they exist
  670. #
  671. self.ylabel = ''
  672. i = 0
  673. for r in self.rasterList:
  674. if self.raster[r]['units'] != '':
  675. self.ylabel += '%s (%d),' % (r['units'], i)
  676. i += 1
  677. if self.ylabel == '':
  678. self.ylabel = _('Raster values')
  679. else:
  680. self.ylabel = self.ylabel.rstrip(',')
  681. def CreateDatalist(self, raster, coords):
  682. """!Build a list of distance, value pairs for points along transect using r.profile
  683. """
  684. datalist = []
  685. # keep total number of transect points to 500 or less to avoid
  686. # freezing with large, high resolution maps
  687. region = grass.region()
  688. curr_res = min(float(region['nsres']),float(region['ewres']))
  689. transect_rec = 0
  690. if self.transect_length / curr_res > 500:
  691. transect_res = self.transect_length / 500
  692. else: transect_res = curr_res
  693. ret = gcmd.RunCommand("r.profile",
  694. parent = self,
  695. input = raster,
  696. profile = coords,
  697. res = transect_res,
  698. null = "nan",
  699. quiet = True,
  700. read = True)
  701. if not ret:
  702. return []
  703. for line in ret.splitlines():
  704. dist, elev = line.strip().split(' ')
  705. if elev != 'nan':
  706. datalist.append((dist,elev))
  707. return datalist
  708. def OnCreateProfile(self, event):
  709. """!Main routine for creating a profile. Uses r.profile to
  710. create a list of distance,cell value pairs. This is passed to
  711. plot to create a line graph of the profile. If the profile
  712. transect is in multiple segments, these are drawn as
  713. points. Profile transect is drawn, using methods in mapdisp.py
  714. """
  715. if len(self.mapwin.polycoords) == 0 or len(self.rasterList) == 0:
  716. dlg = wx.MessageDialog(parent = self,
  717. message = _('You must draw a transect to profile in the map display window.'),
  718. caption = _('Nothing to profile'),
  719. style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  720. dlg.ShowModal()
  721. dlg.Destroy()
  722. return
  723. self.mapwin.SetCursor(self.parent.cursors["default"])
  724. self.SetCursor(self.parent.cursors["default"])
  725. self.SetGraphStyle()
  726. self.SetupProfile()
  727. p = self.CreatePlotList()
  728. self.DrawPlot(p)
  729. # reset transect
  730. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0,0.0)
  731. self.mapwin.mouse['use'] = 'pointer'
  732. self.mapwin.mouse['box'] = 'point'
  733. def CreatePlotList(self):
  734. """!Create a plot data list from transect datalist and
  735. transect segment endpoint coordinates.
  736. """
  737. # graph the distance, value pairs for the transect
  738. self.plotlist = []
  739. # Add segment marker points to plot data list
  740. if len(self.seglist) > 0 :
  741. self.ppoints = plot.PolyMarker(self.seglist,
  742. legend = ' ' + self.properties['marker']['legend'],
  743. colour = wx.Color(self.properties['marker']['color'][0],
  744. self.properties['marker']['color'][1],
  745. self.properties['marker']['color'][2],
  746. 255),
  747. size = self.properties['marker']['size'],
  748. fillstyle = self.ptfilldict[self.properties['marker']['fill']],
  749. marker = self.properties['marker']['type'])
  750. self.plotlist.append(self.ppoints)
  751. # Add profile distance/elevation pairs to plot data list for each raster profiled
  752. for r in self.rasterList:
  753. col = wx.Color(self.raster[r]['pcolor'][0],
  754. self.raster[r]['pcolor'][1],
  755. self.raster[r]['pcolor'][2],
  756. 255)
  757. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  758. colour = col,
  759. width = self.raster[r]['pwidth'],
  760. style = self.pstyledict[self.raster[r]['pstyle']],
  761. legend = self.raster[r]['plegend'])
  762. self.plotlist.append(self.raster[r]['pline'])
  763. if len(self.plotlist) > 0:
  764. return self.plotlist
  765. else:
  766. return None
  767. def Update(self):
  768. """!Update profile after changing options
  769. """
  770. self.SetGraphStyle()
  771. p = self.CreatePlotList()
  772. self.DrawPlot(p)
  773. def SaveProfileToFile(self, event):
  774. """!Save r.profile data to a csv file
  775. """
  776. wildcard = _("Comma separated value (*.csv)|*.csv")
  777. dlg = wx.FileDialog(parent = self,
  778. message = _("Path and prefix (for raster name) to save profile values..."),
  779. defaultDir = os.getcwd(),
  780. defaultFile = "", wildcard = wildcard, style = wx.SAVE)
  781. if dlg.ShowModal() == wx.ID_OK:
  782. path = dlg.GetPath()
  783. for r in self.rasterList:
  784. pfile = path+'_'+str(r['name'])+'.csv'
  785. try:
  786. file = open(pfile, "w")
  787. except IOError:
  788. wx.MessageBox(parent = self,
  789. message = _("Unable to open file <%s> for writing.") % pfile,
  790. caption = _("Error"), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  791. return False
  792. for datapair in self.raster[r]['datalist']:
  793. file.write('%d,%d\n' % (float(datapair[0]),float(datapair[1])))
  794. file.close()
  795. dlg.Destroy()