sampling_frame.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. """
  2. @package rlisetup.sampling_frame
  3. @brief r.li.setup - draw sample frame
  4. Classes:
  5. - sampling_frame::RLiSetupMapPanel
  6. - sampling_frame::RLiSetupToolbar
  7. - sampling_frame::GraphicsSetItem
  8. (C) 2013 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 Anna Petrasova <kratochanna gmail.com>
  12. """
  13. import os
  14. import wx
  15. import wx.aui
  16. # start new import
  17. import tempfile
  18. from core.gcmd import RunCommand
  19. import grass.script.core as grass
  20. from core import gcmd
  21. from core.giface import StandaloneGrassInterface
  22. from mapwin.base import MapWindowProperties
  23. from mapwin.buffered import BufferedMapWindow
  24. from core.render import Map
  25. from gui_core.toolbars import BaseToolbar, BaseIcons, ToolSwitcher
  26. from icons.icon import MetaIcon
  27. from core.gcmd import GMessage
  28. from grass.pydispatch.signal import Signal
  29. from grass.pydispatch.errors import DispatcherKeyError
  30. from .functions import SamplingType, checkMapExists
  31. class Circle:
  32. def __init__(self, pt, r):
  33. self.point = pt
  34. self.radius = r
  35. class MaskedArea(object):
  36. def __init__(self, region, raster, radius):
  37. self.region = region
  38. self.raster = raster
  39. self.radius = radius
  40. class RLiSetupMapPanel(wx.Panel):
  41. """Panel with mapwindow used in r.li.setup"""
  42. def __init__(self, parent, samplingType, icon=None, map_=None):
  43. wx.Panel.__init__(self, parent=parent)
  44. self.mapWindowProperties = MapWindowProperties()
  45. self.mapWindowProperties.setValuesFromUserSettings()
  46. giface = StandaloneGrassInterface()
  47. self.samplingtype = samplingType
  48. self.parent = parent
  49. if map_:
  50. self.map_ = map_
  51. else:
  52. self.map_ = Map()
  53. self.map_.region = self.map_.GetRegion()
  54. self._mgr = wx.aui.AuiManager(self)
  55. self.mapWindow = BufferedMapWindow(
  56. parent=self,
  57. giface=giface,
  58. Map=self.map_,
  59. properties=self.mapWindowProperties,
  60. )
  61. self._mgr.AddPane(
  62. self.mapWindow,
  63. wx.aui.AuiPaneInfo()
  64. .CentrePane()
  65. .Dockable(True)
  66. .BestSize((-1, -1))
  67. .Name("mapwindow")
  68. .CloseButton(False)
  69. .DestroyOnClose(True)
  70. .Layer(0),
  71. )
  72. self._toolSwitcher = ToolSwitcher()
  73. self._toolSwitcher.toggleToolChanged.connect(self._onToolChanged)
  74. self.toolbar = RLiSetupToolbar(self, self._toolSwitcher)
  75. self.catId = 1
  76. self._mgr.AddPane(
  77. self.toolbar,
  78. wx.aui.AuiPaneInfo()
  79. .Name("maptoolbar")
  80. .Caption(_("Map Toolbar"))
  81. .ToolbarPane()
  82. .Left()
  83. .Name("mapToolbar")
  84. .CloseButton(False)
  85. .Layer(1)
  86. .Gripper(False)
  87. .BestSize((self.toolbar.GetBestSize())),
  88. )
  89. self._mgr.Update()
  90. if self.samplingtype == SamplingType.REGIONS:
  91. self.afterRegionDrawn = Signal("RLiSetupMapPanel.afterRegionDrawn")
  92. self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(
  93. graphicsType="line"
  94. )
  95. elif self.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]:
  96. self.sampleFrameChanged = Signal("RLiSetupMapPanel.sampleFrameChanged")
  97. self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(
  98. graphicsType="rectangle"
  99. )
  100. elif self.samplingtype in [SamplingType.MUNITSC, SamplingType.MMVWINC]:
  101. self.afterCircleDrawn = Signal("RLiSetupMapPanel.afterCircleDrawn")
  102. self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(
  103. graphicsType="line"
  104. )
  105. else:
  106. self.sampleFrameChanged = Signal("RLiSetupMapPanel.sampleFrameChanged")
  107. self._registeredGraphics = self.mapWindow.RegisterGraphicsToDraw(
  108. graphicsType="rectangle"
  109. )
  110. self._registeredGraphics.AddPen(
  111. "rlisetup", wx.Pen(wx.GREEN, width=2, style=wx.SOLID)
  112. )
  113. self._registeredGraphics.AddItem(
  114. coords=[[0, 0], [0, 0]], penName="rlisetup", hide=True
  115. )
  116. if self.samplingtype != SamplingType.VECT:
  117. self.toolbar.SelectDefault()
  118. def GetMap(self):
  119. return self.map_
  120. def OnPan(self, event):
  121. """Panning, set mouse to drag."""
  122. self.mapWindow.SetModePan()
  123. def OnZoomIn(self, event):
  124. """Zoom in the map."""
  125. self.mapWindow.SetModeZoomIn()
  126. def OnZoomOut(self, event):
  127. """Zoom out the map."""
  128. self.mapWindow.SetModeZoomOut()
  129. def OnZoomToMap(self, event):
  130. layers = self.map_.GetListOfLayers()
  131. self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True)
  132. def OnDrawRadius(self, event):
  133. """Start draw mode"""
  134. self.mapWindow.mouse["use"] = "None"
  135. self.mapWindow.mouse["box"] = "line"
  136. self.mapWindow.pen = wx.Pen(colour=wx.RED, width=1, style=wx.SHORT_DASH)
  137. self.mapWindow.SetNamedCursor("cross")
  138. self.mapWindow.mouseLeftUp.connect(self._radiusDrawn)
  139. def OnDigitizeRegion(self, event):
  140. """Start draw mode"""
  141. self.mapWindow.mouse["use"] = "None"
  142. self.mapWindow.mouse["box"] = "line"
  143. self.mapWindow.pen = wx.Pen(colour=wx.RED, width=1, style=wx.SHORT_DASH)
  144. self.mapWindow.SetNamedCursor("cross")
  145. self.mapWindow.mouseLeftUp.connect(self._lineSegmentDrawn)
  146. self.mapWindow.mouseDClick.connect(self._mouseDbClick)
  147. self._registeredGraphics.GetItem(0).SetCoords([])
  148. def OnDraw(self, event):
  149. """Start draw mode"""
  150. self.mapWindow.mouse["use"] = "None"
  151. self.mapWindow.mouse["box"] = "box"
  152. self.mapWindow.pen = wx.Pen(colour=wx.RED, width=2, style=wx.SHORT_DASH)
  153. self.mapWindow.SetNamedCursor("cross")
  154. self.mapWindow.mouseLeftUp.connect(self._rectangleDrawn)
  155. def _lineSegmentDrawn(self, x, y):
  156. item = self._registeredGraphics.GetItem(0)
  157. coords = item.GetCoords()
  158. if len(coords) == 0:
  159. coords.extend([self.mapWindow.Pixel2Cell(self.mapWindow.mouse["begin"])])
  160. coords.extend([[x, y]])
  161. item.SetCoords(coords)
  162. item.SetPropertyVal("hide", False)
  163. self.mapWindow.ClearLines()
  164. self._registeredGraphics.Draw()
  165. def _mouseDbClick(self, x, y):
  166. item = self._registeredGraphics.GetItem(0)
  167. coords = item.GetCoords()
  168. coords.extend([[x, y]])
  169. item.SetCoords(coords)
  170. item.SetPropertyVal("hide", False)
  171. self.mapWindow.ClearLines()
  172. self._registeredGraphics.Draw()
  173. self.createRegion()
  174. def createRegion(self):
  175. dlg = wx.TextEntryDialog(
  176. None, "Name of sample region", "Create region", "region" + str(self.catId)
  177. )
  178. ret = dlg.ShowModal()
  179. while True:
  180. if ret == wx.ID_OK:
  181. raster = dlg.GetValue()
  182. if checkMapExists(raster):
  183. GMessage(
  184. parent=self,
  185. message=_(
  186. "The raster file %s already" " exists, please change name"
  187. )
  188. % raster,
  189. )
  190. ret = dlg.ShowModal()
  191. else:
  192. dlg.Destroy()
  193. marea = self.writeArea(
  194. self._registeredGraphics.GetItem(0).GetCoords(), raster
  195. )
  196. self.nextRegion(next=True, area=marea)
  197. break
  198. else:
  199. self.nextRegion(next=False)
  200. break
  201. def nextRegion(self, next=True, area=None):
  202. self.mapWindow.ClearLines()
  203. item = self._registeredGraphics.GetItem(0)
  204. item.SetCoords([])
  205. item.SetPropertyVal("hide", True)
  206. layers = self.map_.GetListOfLayers()
  207. self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True)
  208. if next is True:
  209. self.afterRegionDrawn.emit(marea=area)
  210. else:
  211. gcmd.GMessage(
  212. parent=self.parent,
  213. message=_("Raster map not created. Please redraw region."),
  214. )
  215. def writeArea(self, coords, rasterName):
  216. polyfile = tempfile.NamedTemporaryFile(delete=False)
  217. polyfile.write("AREA\n")
  218. for coor in coords:
  219. east, north = coor
  220. point = " %s %s\n" % (east, north)
  221. polyfile.write(point)
  222. catbuf = "=%d a\n" % self.catId
  223. polyfile.write(catbuf)
  224. self.catId = self.catId + 1
  225. polyfile.close()
  226. region_settings = grass.parse_command("g.region", flags="p", delimiter=":")
  227. pname = polyfile.name.split("/")[-1]
  228. tmpraster = "rast_" + pname
  229. tmpvector = "vect_" + pname
  230. wx.BeginBusyCursor()
  231. wx.GetApp().Yield()
  232. RunCommand(
  233. "r.in.poly",
  234. input=polyfile.name,
  235. output=tmpraster,
  236. rows=region_settings["rows"],
  237. overwrite=True,
  238. )
  239. RunCommand(
  240. "r.to.vect", input=tmpraster, output=tmpvector, type="area", overwrite=True
  241. )
  242. RunCommand("v.to.rast", input=tmpvector, output=rasterName, value=1, use="val")
  243. wx.EndBusyCursor()
  244. grass.use_temp_region()
  245. grass.run_command("g.region", vector=tmpvector)
  246. region = grass.region()
  247. marea = MaskedArea(region, rasterName)
  248. RunCommand("g.remove", flags="f", type="raster", name=tmpraster)
  249. RunCommand("g.remove", flags="f", type="vector", name=tmpvector)
  250. os.unlink(polyfile.name)
  251. return marea
  252. def _onToolChanged(self):
  253. """Helper function to disconnect drawing"""
  254. try:
  255. self.mapWindow.mouseLeftUp.disconnect(self._rectangleDrawn)
  256. self.mapWindow.mouseLeftUp.disconnect(self._radiusDrawn)
  257. self.mapWindow.mouseMoving.disconnect(self._mouseMoving)
  258. self.mapWindow.mouseLeftDown.disconnect(self._mouseLeftDown)
  259. self.mapWindow.mouseDClick.disconnect(self._mouseDbClick)
  260. except DispatcherKeyError:
  261. pass
  262. def _radiusDrawn(self, x, y):
  263. """When drawing finished, get region values"""
  264. mouse = self.mapWindow.mouse
  265. item = self._registeredGraphics.GetItem(0)
  266. p1 = mouse["begin"]
  267. p2 = mouse["end"]
  268. dist, (north, east) = self.mapWindow.Distance(p1, p2, False)
  269. circle = Circle(p1, dist)
  270. self.mapWindow.ClearLines()
  271. self.mapWindow.pdcTmp.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT))
  272. pen = wx.Pen(colour=wx.RED, width=2)
  273. self.mapWindow.pdcTmp.SetPen(pen)
  274. self.mapWindow.pdcTmp.DrawCircle(
  275. circle.point[0], circle.point[1], circle.radius
  276. )
  277. self._registeredGraphics.Draw()
  278. self.createCricle(circle)
  279. def createCricle(self, c):
  280. dlg = wx.TextEntryDialog(
  281. None,
  282. "Name of sample circle region",
  283. "Create circle region",
  284. "circle" + str(self.catId),
  285. )
  286. ret = dlg.ShowModal()
  287. while True:
  288. if ret == wx.ID_OK:
  289. raster = dlg.GetValue()
  290. if checkMapExists(raster):
  291. GMessage(
  292. parent=self,
  293. message=_(
  294. "The raster file %s already" " exists, please change name"
  295. )
  296. % raster,
  297. )
  298. ret = dlg.ShowModal()
  299. else:
  300. dlg.Destroy()
  301. circle = self.writeCircle(c, raster)
  302. self.nextCircle(next=True, circle=circle)
  303. break
  304. else:
  305. self.nextCircle(next=False)
  306. break
  307. def nextCircle(self, next=True, circle=None):
  308. self.mapWindow.ClearLines()
  309. item = self._registeredGraphics.GetItem(0)
  310. item.SetPropertyVal("hide", True)
  311. layers = self.map_.GetListOfLayers()
  312. self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True)
  313. if next is True:
  314. self.afterCircleDrawn.emit(region=circle)
  315. else:
  316. gcmd.GMessage(
  317. parent=self.parent,
  318. message=_("Raster map not created. redraw region again."),
  319. )
  320. def writeCircle(self, circle, rasterName):
  321. coords = self.mapWindow.Pixel2Cell(circle.point)
  322. RunCommand(
  323. "r.circle",
  324. output=rasterName,
  325. max=circle.radius,
  326. coordinate=coords,
  327. flags="b",
  328. )
  329. grass.use_temp_region()
  330. grass.run_command("g.region", zoom=rasterName)
  331. region = grass.region()
  332. marea = MaskedArea(region, rasterName, circle.radius)
  333. return marea
  334. def _rectangleDrawn(self):
  335. """When drawing finished, get region values"""
  336. mouse = self.mapWindow.mouse
  337. item = self._registeredGraphics.GetItem(0)
  338. p1 = self.mapWindow.Pixel2Cell(mouse["begin"])
  339. p2 = self.mapWindow.Pixel2Cell(mouse["end"])
  340. item.SetCoords([p1, p2])
  341. region = {
  342. "n": max(p1[1], p2[1]),
  343. "s": min(p1[1], p2[1]),
  344. "w": min(p1[0], p2[0]),
  345. "e": max(p1[0], p2[0]),
  346. }
  347. item.SetPropertyVal("hide", False)
  348. self.mapWindow.ClearLines()
  349. self._registeredGraphics.Draw()
  350. if self.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]:
  351. dlg = wx.MessageDialog(
  352. self,
  353. "Is this area ok?",
  354. "select sampling unit",
  355. wx.YES_NO | wx.ICON_QUESTION,
  356. )
  357. ret = dlg.ShowModal()
  358. if ret == wx.ID_YES:
  359. grass.use_temp_region()
  360. grass.run_command(
  361. "g.region",
  362. n=region["n"],
  363. s=region["s"],
  364. e=region["e"],
  365. w=region["w"],
  366. )
  367. tregion = grass.region()
  368. self.sampleFrameChanged.emit(region=tregion)
  369. self.mapWindow.ClearLines()
  370. item = self._registeredGraphics.GetItem(0)
  371. item.SetPropertyVal("hide", True)
  372. layers = self.map_.GetListOfLayers()
  373. self.mapWindow.ZoomToMap(layers=layers, ignoreNulls=False, render=True)
  374. else:
  375. self.nextRegion(next=False)
  376. dlg.Destroy()
  377. elif self.samplingtype != SamplingType.WHOLE:
  378. """When drawing finished, get region values"""
  379. self.sampleFrameChanged.emit(region=region)
  380. icons = {
  381. "draw": MetaIcon(
  382. img="edit",
  383. label=_("Draw sampling frame"),
  384. desc=_("Draw sampling frame by clicking and dragging"),
  385. ),
  386. "digitizeunit": MetaIcon(
  387. img="edit",
  388. label=_("Draw sampling rectangle"),
  389. desc=_("Draw sampling rectangle by clicking and dragging"),
  390. ),
  391. "digitizeunitc": MetaIcon(
  392. img="line-create",
  393. label=_("Draw sampling circle"),
  394. desc=_("Draw sampling circle radius by clicking and dragging"),
  395. ),
  396. "digitizeregion": MetaIcon(
  397. img="polygon-create",
  398. label=_("Draw sampling region"),
  399. desc=_("Draw sampling region by polygon. Right Double click to end drawing"),
  400. ),
  401. }
  402. class RLiSetupToolbar(BaseToolbar):
  403. """IClass toolbar"""
  404. def __init__(self, parent, toolSwitcher):
  405. """RLiSetup toolbar constructor"""
  406. BaseToolbar.__init__(
  407. self, parent, toolSwitcher, style=wx.NO_BORDER | wx.TB_VERTICAL
  408. )
  409. self.InitToolbar(self._toolbarData())
  410. if self.parent.samplingtype == SamplingType.REGIONS:
  411. self._default = self.digitizeregion
  412. elif self.parent.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]:
  413. self._default = self.digitizeunit
  414. elif self.parent.samplingtype in [SamplingType.MUNITSC, SamplingType.MMVWINC]:
  415. self._default = self.digitizeunitc
  416. elif self.parent.samplingtype == SamplingType.VECT:
  417. self._default = None
  418. else:
  419. self._default = self.draw
  420. for tool in (self._default, self.pan, self.zoomIn, self.zoomOut):
  421. if tool:
  422. self.toolSwitcher.AddToolToGroup(
  423. group="mouseUse", toolbar=self, tool=tool
  424. )
  425. # realize the toolbar
  426. self.Realize()
  427. def _toolbarData(self):
  428. """Toolbar data"""
  429. if self.parent.samplingtype == SamplingType.REGIONS:
  430. drawTool = (
  431. ("digitizeregion", icons["digitizeregion"].label),
  432. icons["digitizeregion"],
  433. self.parent.OnDigitizeRegion,
  434. wx.ITEM_CHECK,
  435. )
  436. elif self.parent.samplingtype in [SamplingType.MUNITSR, SamplingType.MMVWINR]:
  437. drawTool = (
  438. ("digitizeunit", icons["digitizeunit"].label),
  439. icons["digitizeunit"],
  440. self.parent.OnDraw,
  441. wx.ITEM_CHECK,
  442. )
  443. elif self.parent.samplingtype in [SamplingType.MUNITSC, SamplingType.MMVWINC]:
  444. drawTool = (
  445. ("digitizeunitc", icons["digitizeunitc"].label),
  446. icons["digitizeunitc"],
  447. self.parent.OnDrawRadius,
  448. wx.ITEM_CHECK,
  449. )
  450. else:
  451. drawTool = (
  452. ("draw", icons["draw"].label),
  453. icons["draw"],
  454. self.parent.OnDraw,
  455. wx.ITEM_CHECK,
  456. )
  457. if self.parent.samplingtype == SamplingType.VECT:
  458. return self._getToolbarData(
  459. (
  460. (
  461. ("pan", BaseIcons["pan"].label),
  462. BaseIcons["pan"],
  463. self.parent.OnPan,
  464. wx.ITEM_CHECK,
  465. ),
  466. (
  467. ("zoomIn", BaseIcons["zoomIn"].label),
  468. BaseIcons["zoomIn"],
  469. self.parent.OnZoomIn,
  470. wx.ITEM_CHECK,
  471. ),
  472. (
  473. ("zoomOut", BaseIcons["zoomOut"].label),
  474. BaseIcons["zoomOut"],
  475. self.parent.OnZoomOut,
  476. wx.ITEM_CHECK,
  477. ),
  478. (
  479. ("zoomExtent", BaseIcons["zoomExtent"].label),
  480. BaseIcons["zoomExtent"],
  481. self.parent.OnZoomToMap,
  482. ),
  483. )
  484. )
  485. else:
  486. return self._getToolbarData(
  487. (
  488. drawTool,
  489. (None,),
  490. (
  491. ("pan", BaseIcons["pan"].label),
  492. BaseIcons["pan"],
  493. self.parent.OnPan,
  494. wx.ITEM_CHECK,
  495. ),
  496. (
  497. ("zoomIn", BaseIcons["zoomIn"].label),
  498. BaseIcons["zoomIn"],
  499. self.parent.OnZoomIn,
  500. wx.ITEM_CHECK,
  501. ),
  502. (
  503. ("zoomOut", BaseIcons["zoomOut"].label),
  504. BaseIcons["zoomOut"],
  505. self.parent.OnZoomOut,
  506. wx.ITEM_CHECK,
  507. ),
  508. (
  509. ("zoomExtent", BaseIcons["zoomExtent"].label),
  510. BaseIcons["zoomExtent"],
  511. self.parent.OnZoomToMap,
  512. ),
  513. )
  514. )