mapwindow.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. """
  2. @package vdigit.mapwindow
  3. @brief Map display canvas for wxGUI vector digitizer
  4. Classes:
  5. - mapwindow::VDigitWindow
  6. (C) 2011-2013 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Martin Landa <landa.martin gmail.com>
  10. """
  11. import wx
  12. import tempfile
  13. import six
  14. from grass.pydispatch.signal import Signal
  15. from dbmgr.dialogs import DisplayAttributesDialog
  16. from core.gcmd import RunCommand, GMessage, GError
  17. from core.debug import Debug
  18. from mapwin.buffered import BufferedMapWindow
  19. from core.settings import UserSettings
  20. from core.utils import ListOfCatsToRange
  21. from core.units import ConvertValue as UnitsConvertValue
  22. from core.globalvar import QUERYLAYER
  23. from vdigit.dialogs import (
  24. VDigitCategoryDialog,
  25. VDigitZBulkDialog,
  26. VDigitDuplicatesDialog,
  27. )
  28. from gui_core import gselect
  29. from gui_core.wrap import PseudoDC, NewId
  30. class VDigitWindow(BufferedMapWindow):
  31. """A Buffered window extended for vector digitizer."""
  32. def __init__(
  33. self,
  34. parent,
  35. giface,
  36. Map,
  37. properties,
  38. tree=None,
  39. id=wx.ID_ANY,
  40. lmgr=None,
  41. style=wx.NO_FULL_REPAINT_ON_RESIZE,
  42. **kwargs,
  43. ):
  44. BufferedMapWindow.__init__(
  45. self,
  46. parent=parent,
  47. giface=giface,
  48. Map=Map,
  49. properties=properties,
  50. style=style,
  51. **kwargs,
  52. )
  53. self.lmgr = lmgr
  54. self.tree = tree
  55. self.pdcVector = PseudoDC()
  56. self.toolbar = self.parent.GetToolbar("vdigit")
  57. self.digit = None # wxvdigit.IVDigit
  58. self._digitizingInfo = False # digitizing with info
  59. # Emitted when info about digitizing updated
  60. # Parameter text is a string with information
  61. # currently used only for coordinates of mouse cursor + segmnt and
  62. # total feature length
  63. self.digitizingInfo = Signal("VDigitWindow.digitizingInfo")
  64. # Emitted when some info about digitizing is or will be availbale
  65. self.digitizingInfoAvailable = Signal("VDigitWindow.digitizingInfo")
  66. # Emitted when some info about digitizing is or will be availbale
  67. # digitizingInfo signal is emitted only between digitizingInfoAvailable
  68. # and digitizingInfoUnavailable signals
  69. self.digitizingInfoUnavailable = Signal("VDigitWindow.digitizingInfo")
  70. self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  71. self.mouseMoving.connect(self._mouseMovingToDigitizingInfo)
  72. def GetDisplay(self):
  73. if self.digit:
  74. return self.digit.GetDisplay()
  75. return None
  76. def GetDigit(self):
  77. """Get digit class"""
  78. return self.digit
  79. def SetToolbar(self, toolbar):
  80. """Set up related toolbar"""
  81. self.toolbar = toolbar
  82. def _mouseMovingToDigitizingInfo(self, x, y):
  83. e, n = x, y
  84. precision = int(
  85. UserSettings.Get(group="projection", key="format", subkey="precision")
  86. )
  87. if (
  88. self.toolbar.GetAction() != "addLine"
  89. or self.toolbar.GetAction("type") not in ("line", "boundary")
  90. or len(self.polycoords) == 0
  91. ):
  92. # we cannot provide info, so find out if it is something new
  93. if self._digitizingInfo:
  94. self._digitizingInfo = False
  95. self.digitizingInfoUnavailable.emit()
  96. return
  97. # else, we can provide info, so find out if it is first time
  98. if not self._digitizingInfo:
  99. self._digitizingInfo = True
  100. self.digitizingInfoAvailable.emit()
  101. # for linear feature show segment and total length
  102. distance_seg = self.Distance(self.polycoords[-1], (e, n), screen=False)[0]
  103. distance_tot = distance_seg
  104. for idx in range(1, len(self.polycoords)):
  105. distance_tot += self.Distance(
  106. self.polycoords[idx - 1], self.polycoords[idx], screen=False
  107. )[0]
  108. text = "seg: %.*f; tot: %.*f" % (
  109. precision,
  110. distance_seg,
  111. precision,
  112. distance_tot,
  113. )
  114. self.digitizingInfo.emit(text=text)
  115. def OnKeyDown(self, event):
  116. """Key pressed"""
  117. shift = event.ShiftDown()
  118. kc = event.GetKeyCode()
  119. event = None
  120. if not shift:
  121. if kc == ord("P"):
  122. event = wx.CommandEvent(winid=self.toolbar.addPoint)
  123. tool = self.toolbar.OnAddPoint
  124. elif kc == ord("L"):
  125. event = wx.CommandEvent(winid=self.toolbar.addLine)
  126. tool = self.toolbar.OnAddLine
  127. if event:
  128. self.toolbar.OnTool(event)
  129. tool(event)
  130. def _updateMap(self):
  131. if not self.toolbar or not self.toolbar.GetLayer():
  132. return
  133. # set region
  134. self.digit.GetDisplay().UpdateRegion()
  135. # re-calculate threshold for digitization tool
  136. # self.parent.digit.GetDisplay().GetThreshold()
  137. # draw map
  138. # self.pdcVector.Clear()
  139. self.pdcVector.RemoveAll()
  140. item = None
  141. if self.tree:
  142. try:
  143. item = self.tree.FindItemByData("maplayer", self.toolbar.GetLayer())
  144. except TypeError:
  145. pass
  146. if not self.tree or (self.tree and item and self.tree.IsItemChecked(item)):
  147. self.redrawAll = True
  148. self.digit.GetDisplay().DrawMap()
  149. # translate tmp objects (pointer position)
  150. if self.toolbar.GetAction() == "moveLine" and hasattr(self, "moveInfo"):
  151. if "beginDiff" in self.moveInfo:
  152. # move line
  153. for id in self.moveInfo["id"]:
  154. self.pdcTmp.TranslateId(
  155. id, self.moveInfo["beginDiff"][0], self.moveInfo["beginDiff"][1]
  156. )
  157. del self.moveInfo["beginDiff"]
  158. def OnLeftDownAddLine(self, event):
  159. """Left mouse button pressed - add new feature"""
  160. try:
  161. mapLayer = self.toolbar.GetLayer().GetName()
  162. except:
  163. return
  164. if self.toolbar.GetAction("type") in ["point", "centroid"]:
  165. # add new point / centroiud
  166. east, north = self.Pixel2Cell(self.mouse["begin"])
  167. nfeat, fids = self.digit.AddFeature(
  168. self.toolbar.GetAction("type"), [(east, north)]
  169. )
  170. if nfeat < 1:
  171. return
  172. self.UpdateMap(render=False) # redraw map
  173. # add new record into attribute table
  174. if UserSettings.Get(group="vdigit", key="addRecord", subkey="enabled"):
  175. # select attributes based on layer and category
  176. cats = {
  177. fids[0]: {
  178. UserSettings.Get(group="vdigit", key="layer", subkey="value"): (
  179. UserSettings.Get(
  180. group="vdigit", key="category", subkey="value"
  181. ),
  182. )
  183. }
  184. }
  185. posWindow = self.ClientToScreen(
  186. (
  187. self.mouse["end"][0] + self.dialogOffset,
  188. self.mouse["end"][1] + self.dialogOffset,
  189. )
  190. )
  191. addRecordDlg = DisplayAttributesDialog(
  192. parent=self,
  193. map=mapLayer,
  194. cats=cats,
  195. pos=posWindow,
  196. action="add",
  197. ignoreError=True,
  198. )
  199. if self.toolbar.GetAction("type") == "centroid":
  200. for fid in fids:
  201. self._geomAttrb(fid, addRecordDlg, "area")
  202. self._geomAttrb(fid, addRecordDlg, "perimeter")
  203. if addRecordDlg.IsFound():
  204. addRecordDlg.ShowModal()
  205. addRecordDlg.Destroy()
  206. elif self.toolbar.GetAction("type") in ["line", "boundary", "area"]:
  207. # add new point to the line
  208. self.polycoords.append(self.Pixel2Cell(event.GetPosition()))
  209. self.DrawLines(pdc=self.pdcTmp)
  210. def _geomAttrb(self, fid, dialog, attrb):
  211. """Define geometry attributes"""
  212. mapLayer = self.toolbar.GetLayer()
  213. if self.tree:
  214. item = self.tree.FindItemByData("maplayer", mapLayer)
  215. vdigit = self.tree.GetLayerInfo(item, key="vdigit")
  216. else:
  217. item = vdigit = None
  218. if not vdigit or "geomAttr" not in vdigit or attrb not in vdigit["geomAttr"]:
  219. return
  220. val = -1
  221. if attrb == "length":
  222. val = self.digit.GetLineLength(fid)
  223. type = attrb
  224. elif attrb == "area":
  225. val = self.digit.GetAreaSize(fid)
  226. type = attrb
  227. elif attrb == "perimeter":
  228. val = self.digit.GetAreaPerimeter(fid)
  229. type = "length"
  230. if val > 0:
  231. layer = int(UserSettings.Get(group="vdigit", key="layer", subkey="value"))
  232. column = vdigit["geomAttr"][attrb]["column"]
  233. val = UnitsConvertValue(val, type, vdigit["geomAttr"][attrb]["units"])
  234. dialog.SetColumnValue(layer, column, val)
  235. dialog.OnReset()
  236. def _geomAttrbUpdate(self, fids):
  237. """Update geometry atrributes of currently selected features
  238. :param fid: list feature id
  239. """
  240. mapLayer = self.parent.toolbars["vdigit"].GetLayer()
  241. vectorName = mapLayer.GetName()
  242. if self.tree:
  243. item = self.tree.FindItemByData("maplayer", mapLayer)
  244. vdigit = self.tree.GetLayerInfo(item, key="vdigit")
  245. else:
  246. item = vdigit = None
  247. if not vdigit or "geomAttr" not in vdigit:
  248. return
  249. dbInfo = gselect.VectorDBInfo(vectorName)
  250. sqlfile = tempfile.NamedTemporaryFile(mode="w")
  251. for fid in fids:
  252. for layer, cats in six.iteritems(self.digit.GetLineCats(fid)):
  253. table = dbInfo.GetTable(layer)
  254. for attrb, item in six.iteritems(vdigit["geomAttr"]):
  255. val = -1
  256. if attrb == "length":
  257. val = self.digit.GetLineLength(fid)
  258. type = attrb
  259. elif attrb == "area":
  260. val = self.digit.GetAreaSize(fid)
  261. type = attrb
  262. elif attrb == "perimeter":
  263. val = self.digit.GetAreaPerimeter(fid)
  264. type = "length"
  265. if val < 0:
  266. continue
  267. val = UnitsConvertValue(val, type, item["units"])
  268. for cat in cats:
  269. sqlfile.write(
  270. "UPDATE %s SET %s = %f WHERE %s = %d;\n"
  271. % (
  272. table,
  273. item["column"],
  274. val,
  275. dbInfo.GetKeyColumn(layer),
  276. cat,
  277. )
  278. )
  279. sqlfile.file.flush()
  280. RunCommand("db.execute", parent=True, quiet=True, input=sqlfile.name)
  281. def _updateATM(self):
  282. """Update open Attribute Table Manager
  283. .. todo::
  284. use AddDataRow() instead
  285. """
  286. if not self.lmgr:
  287. return
  288. # update ATM
  289. digitVector = self.toolbar.GetLayer().GetName()
  290. for atm in self.lmgr.dialogs["atm"]:
  291. atmVector = atm.GetVectorName()
  292. if atmVector == digitVector:
  293. layer = UserSettings.Get(group="vdigit", key="layer", subkey="value")
  294. # TODO: use AddDataRow instead
  295. atm.LoadData(layer)
  296. def OnLeftDownEditLine(self, event):
  297. """Left mouse button pressed - edit linear feature - add new
  298. vertex.
  299. """
  300. self.polycoords.append(self.Pixel2Cell(self.mouse["begin"]))
  301. self.moveInfo["id"].append(NewId())
  302. self.DrawLines(pdc=self.pdcTmp)
  303. def OnLeftDownMoveLine(self, event):
  304. """Left mouse button pressed - vector digitizer move
  305. feature/vertex, edit linear feature
  306. """
  307. self.moveInfo = dict()
  308. # geographic coordinates of initial position (left-down)
  309. self.moveInfo["begin"] = None
  310. # list of ids to modify
  311. self.moveInfo["id"] = list()
  312. # set pen
  313. if self.toolbar.GetAction() in ["moveVertex", "editLine"]:
  314. pcolor = UserSettings.Get(
  315. group="vdigit", key="symbol", subkey=["highlight", "color"]
  316. )
  317. self.pen = self.polypen = wx.Pen(
  318. colour=pcolor, width=2, style=wx.SHORT_DASH
  319. )
  320. self.pdcTmp.SetPen(self.polypen)
  321. def OnLeftDownDisplayCA(self, event):
  322. """Left mouse button pressed - vector digitizer display categories
  323. or attributes action
  324. """
  325. try:
  326. mapLayer = self.toolbar.GetLayer().GetName()
  327. except:
  328. return
  329. coords = self.Pixel2Cell(self.mouse["begin"])
  330. # unselect
  331. self.digit.GetDisplay().SetSelected([])
  332. # select feature by point
  333. cats = {}
  334. self.digit.GetDisplay().SelectLineByPoint(coords)
  335. if not self.digit.GetDisplay().GetSelected():
  336. for key in ("attributes", "category"):
  337. if self.parent.dialogs[key] and self.parent.dialogs[key].IsShown():
  338. self.parent.dialogs[key].Hide()
  339. self.UpdateMap(render=False, renderVector=True)
  340. return
  341. if UserSettings.Get(group="vdigit", key="checkForDupl", subkey="enabled"):
  342. lines = self.digit.GetDisplay().GetSelected()
  343. else:
  344. lines = (self.digit.GetDisplay().GetSelected()[0],) # only first found
  345. for line in lines:
  346. cats[line] = self.digit.GetLineCats(line)
  347. posWindow = self.ClientToScreen(
  348. (
  349. self.mouse["end"][0] + self.dialogOffset,
  350. self.mouse["end"][1] + self.dialogOffset,
  351. )
  352. )
  353. if self.toolbar.GetAction() == "displayAttrs":
  354. # select attributes based on coordinates (all layers)
  355. if self.parent.dialogs["attributes"] is None:
  356. self.parent.dialogs["attributes"] = DisplayAttributesDialog(
  357. parent=self, map=mapLayer, cats=cats, action="update"
  358. )
  359. else:
  360. # upgrade dialog
  361. self.parent.dialogs["attributes"].UpdateDialog(cats=cats)
  362. if (
  363. self.parent.dialogs["attributes"]
  364. and self.parent.dialogs["attributes"].mapDBInfo
  365. ):
  366. if len(cats.keys()) > 0:
  367. # highlight feature & re-draw map
  368. if not self.parent.dialogs["attributes"].IsShown():
  369. self.parent.dialogs["attributes"].Show()
  370. else:
  371. if (
  372. self.parent.dialogs["attributes"]
  373. and self.parent.dialogs["attributes"].IsShown()
  374. ):
  375. self.parent.dialogs["attributes"].Hide()
  376. else: # displayCats
  377. if self.parent.dialogs["category"] is None:
  378. # open new dialog
  379. dlg = VDigitCategoryDialog(
  380. parent=self,
  381. vectorName=mapLayer,
  382. cats=cats,
  383. pos=posWindow,
  384. title=_("Update categories"),
  385. )
  386. self.parent.dialogs["category"] = dlg
  387. else:
  388. # update currently open dialog
  389. self.parent.dialogs["category"].UpdateDialog(cats=cats)
  390. if self.parent.dialogs["category"]:
  391. if len(cats.keys()) > 0:
  392. # highlight feature & re-draw map
  393. if not self.parent.dialogs["category"].IsShown():
  394. self.parent.dialogs["category"].Show()
  395. else:
  396. if self.parent.dialogs["category"].IsShown():
  397. self.parent.dialogs["category"].Hide()
  398. self.UpdateMap(render=False, renderVector=True)
  399. def OnLeftDownCopyCA(self, event):
  400. """Left mouse button pressed - vector digitizer copy
  401. categories or attributes action
  402. """
  403. if not hasattr(self, "copyCatsList"):
  404. self.copyCatsList = []
  405. else:
  406. self.copyCatsIds = []
  407. self.mouse["box"] = "box"
  408. def OnLeftDownCopyLine(self, event):
  409. """Left mouse button pressed - vector digitizer copy lines
  410. action
  411. """
  412. if not hasattr(self, "copyIds"):
  413. self.copyIds = []
  414. self.layerTmp = None
  415. def OnLeftDownBulkLine(self, event):
  416. """Left mouse button pressed - vector digitizer label 3D
  417. vector lines
  418. """
  419. if len(self.polycoords) > 1: # start new line
  420. self.polycoords = []
  421. self.ClearLines(pdc=self.pdcTmp)
  422. self.polycoords.append(self.Pixel2Cell(event.GetPosition()))
  423. if len(self.polycoords) == 1:
  424. begin = self.Pixel2Cell(self.polycoords[-1])
  425. end = self.Pixel2Cell(self.mouse["end"])
  426. else:
  427. end = self.Pixel2Cell(self.polycoords[-1])
  428. begin = self.Pixel2Cell(self.mouse["begin"])
  429. self.DrawLines(self.pdcTmp, polycoords=(begin, end))
  430. def OnLeftDownUndo(self, event):
  431. """Left mouse button pressed with control key - vector
  432. digitizer undo functionality
  433. """
  434. if self.mouse["use"] != "pointer" or not self.toolbar:
  435. return
  436. action = self.toolbar.GetAction()
  437. if (
  438. action == "addLine"
  439. and self.toolbar.GetAction("type") in ["line", "boundary", "area"]
  440. ) or action == "editLine":
  441. # add line or boundary -> remove last point from the line
  442. try:
  443. removed = self.polycoords.pop()
  444. Debug.msg(
  445. 4,
  446. "VDigitWindow.OnMiddleDown(): polycoords_poped=%s"
  447. % [
  448. removed,
  449. ],
  450. )
  451. # self.mouse['begin'] = self.Cell2Pixel(self.polycoords[-1])
  452. except:
  453. pass
  454. if action == "editLine":
  455. # remove last vertex & line
  456. if len(self.moveInfo["id"]) > 1:
  457. self.moveInfo["id"].pop()
  458. self.UpdateMap(render=False, renderVector=False)
  459. elif action in [
  460. "deleteLine",
  461. "deleteArea",
  462. "moveLine",
  463. "splitLine",
  464. "addVertex",
  465. "removeVertex",
  466. "moveVertex",
  467. "copyCats",
  468. "flipLine",
  469. "mergeLine",
  470. "snapLine",
  471. "connectLine",
  472. "copyLine",
  473. "queryLine",
  474. "breakLine",
  475. "typeConv",
  476. ]:
  477. # various tools -> unselected selected features
  478. self.digit.GetDisplay().SetSelected([])
  479. if action in ["moveLine", "moveVertex", "editLine"] and hasattr(
  480. self, "moveInfo"
  481. ):
  482. del self.moveInfo
  483. elif action == "copyCats":
  484. try:
  485. del self.copyCatsList
  486. del self.copyCatsIds
  487. except AttributeError:
  488. pass
  489. elif action == "copyLine":
  490. del self.copyIds
  491. if self.layerTmp:
  492. self.Map.DeleteLayer(self.layerTmp)
  493. self.UpdateMap(render=True, renderVector=False)
  494. del self.layerTmp
  495. self.polycoords = []
  496. self.UpdateMap(render=False) # render vector
  497. elif action == "zbulkLine":
  498. # reset polyline
  499. self.polycoords = []
  500. self.digit.GetDisplay().SetSelected([])
  501. self.UpdateMap(render=False)
  502. self.redrawAll = True
  503. self.UpdateMap(render=False, renderVector=False)
  504. def _onLeftDown(self, event):
  505. """Left mouse button donw - vector digitizer various actions"""
  506. try:
  507. mapLayer = self.toolbar.GetLayer().GetName()
  508. except:
  509. GMessage(parent=self, message=_("No vector map selected for editing."))
  510. event.Skip()
  511. return
  512. action = self.toolbar.GetAction()
  513. if not action:
  514. GMessage(
  515. parent=self,
  516. message=_(
  517. "Nothing to do. " "Choose appropriate tool from digitizer toolbar."
  518. ),
  519. )
  520. event.Skip()
  521. return
  522. if action not in ("moveVertex", "addVertex", "removeVertex", "editLine"):
  523. # set pen
  524. self.pen = wx.Pen(
  525. colour=UserSettings.Get(
  526. group="vdigit", key="symbol", subkey=["newSegment", "color"]
  527. ),
  528. width=2,
  529. style=wx.SHORT_DASH,
  530. )
  531. self.polypen = wx.Pen(
  532. colour=UserSettings.Get(
  533. group="vdigit", key="symbol", subkey=["newLine", "color"]
  534. ),
  535. width=2,
  536. style=wx.SOLID,
  537. )
  538. if action in ("addVertex", "removeVertex", "splitLines"):
  539. # unselect
  540. self.digit.GetDisplay().SetSelected([])
  541. if action == "addLine":
  542. self.OnLeftDownAddLine(event)
  543. elif action == "editLine" and hasattr(self, "moveInfo"):
  544. self.OnLeftDownEditLine(event)
  545. elif action in ("moveLine", "moveVertex", "editLine") and not hasattr(
  546. self, "moveInfo"
  547. ):
  548. self.OnLeftDownMoveLine(event)
  549. elif action in ("displayAttrs" "displayCats"):
  550. self.OnLeftDownDisplayCA(event)
  551. elif action in ("copyCats", "copyAttrs"):
  552. self.OnLeftDownCopyCA(event)
  553. elif action == "copyLine":
  554. self.OnLeftDownCopyLine(event)
  555. elif action == "zbulkLine":
  556. self.OnLeftDownBulkLine(event)
  557. def OnLeftUpVarious(self, event):
  558. """Left mouse button released - vector digitizer various
  559. actions
  560. """
  561. pos1 = self.Pixel2Cell(self.mouse["begin"])
  562. pos2 = self.Pixel2Cell(self.mouse["end"])
  563. nselected = 0
  564. action = self.toolbar.GetAction()
  565. # -> delete line || move line || move vertex
  566. if action in ("moveVertex", "editLine"):
  567. if len(self.digit.GetDisplay().GetSelected()) == 0:
  568. nselected = int(
  569. self.digit.GetDisplay().SelectLineByPoint(pos1)["line"] != -1
  570. )
  571. if action == "editLine":
  572. try:
  573. selVertex = self.digit.GetDisplay().GetSelectedVertex(pos1)[0]
  574. except IndexError:
  575. selVertex = None
  576. if selVertex:
  577. # self.UpdateMap(render=False)
  578. ids = self.digit.GetDisplay().GetSelected(grassId=False)
  579. # move this line to tmp layer
  580. self.polycoords = []
  581. for id in ids:
  582. if id % 2: # register only vertices
  583. e, n = self.Pixel2Cell(
  584. self.pdcVector.GetIdBounds(id)[0:2]
  585. )
  586. self.polycoords.append((e, n))
  587. self.digit.GetDisplay().DrawSelected(False)
  588. if selVertex < ids[-1] / 2:
  589. # choose first or last node of line
  590. self.moveInfo["id"].reverse()
  591. self.polycoords.reverse()
  592. else:
  593. # unselect
  594. self.digit.GetDisplay().SetSelected([])
  595. del self.moveInfo
  596. self.UpdateMap(render=False)
  597. elif action in ("copyCats", "copyAttrs"):
  598. if not hasattr(self, "copyCatsIds"):
  599. # 'from' -> select by point
  600. nselected = int(
  601. self.digit.GetDisplay().SelectLineByPoint(pos1)["line"] != -1
  602. )
  603. if nselected:
  604. self.copyCatsList = self.digit.GetDisplay().GetSelected()
  605. else:
  606. # -> 'to' -> select by bbox
  607. self.digit.GetDisplay().SetSelected([])
  608. # return number of selected features (by box/point)
  609. nselected = self.digit.GetDisplay().SelectLinesByBox((pos1, pos2))
  610. if nselected == 0:
  611. nselected = int(
  612. self.digit.GetDisplay().SelectLineByPoint(pos1)["line"] != -1
  613. )
  614. if nselected > 0:
  615. self.copyCatsIds = self.digit.GetDisplay().GetSelected()
  616. elif action == "queryLine":
  617. selected = self.digit.SelectLinesByQuery(bbox=(pos1, pos2))
  618. nselected = len(selected)
  619. if nselected > 0:
  620. self.digit.GetDisplay().SetSelected(selected)
  621. else:
  622. # -> moveLine || deleteLine, etc. (select by point/box)
  623. if action == "moveLine" and len(self.digit.GetDisplay().GetSelected()) > 0:
  624. nselected = 0
  625. else:
  626. if action == "deleteArea":
  627. nselected = int(
  628. self.digit.GetDisplay().SelectAreaByPoint(pos1)["area"] != -1
  629. )
  630. else:
  631. if action == "moveLine":
  632. drawSeg = True
  633. else:
  634. drawSeg = False
  635. nselected = self.digit.GetDisplay().SelectLinesByBox(
  636. bbox=(pos1, pos2), drawSeg=drawSeg
  637. )
  638. if nselected == 0:
  639. nselected = int(
  640. self.digit.GetDisplay().SelectLineByPoint(pos1)["line"]
  641. != -1
  642. )
  643. if nselected > 0:
  644. if action in ("moveLine", "moveVertex") and hasattr(self, "moveInfo"):
  645. # get pseudoDC id of objects which should be redrawn
  646. if action == "moveLine":
  647. # -> move line
  648. self.moveInfo["id"] = self.digit.GetDisplay().GetSelected(
  649. grassId=False
  650. )
  651. else: # moveVertex
  652. self.moveInfo["id"] = self.digit.GetDisplay().GetSelectedVertex(
  653. pos1
  654. )
  655. if len(self.moveInfo["id"]) == 0: # no vertex found
  656. self.digit.GetDisplay().SetSelected([])
  657. #
  658. # check for duplicates
  659. #
  660. if UserSettings.Get(group="vdigit", key="checkForDupl", subkey="enabled"):
  661. dupl = self.digit.GetDisplay().GetDuplicates()
  662. self.UpdateMap(render=False)
  663. if dupl:
  664. posWindow = self.ClientToScreen(
  665. (
  666. self.mouse["end"][0] + self.dialogOffset,
  667. self.mouse["end"][1] + self.dialogOffset,
  668. )
  669. )
  670. dlg = VDigitDuplicatesDialog(parent=self, data=dupl, pos=posWindow)
  671. if dlg.ShowModal() == wx.ID_OK:
  672. self.digit.GetDisplay().UnSelect(dlg.GetUnSelected())
  673. # update selected
  674. self.UpdateMap(render=False)
  675. if action != "editLine":
  676. # -> move line || move vertex
  677. self.UpdateMap(render=False)
  678. else: # no vector object found
  679. if not (
  680. action in ("moveLine", "moveVertex")
  681. and hasattr(self, "moveInfo")
  682. and len(self.moveInfo["id"]) > 0
  683. ):
  684. # avoid left-click when features are already selected
  685. self.UpdateMap(render=False, renderVector=False)
  686. def OnLeftUpModifyLine(self, event):
  687. """Left mouse button released - vector digitizer split line,
  688. add/remove vertex action
  689. """
  690. pos1 = self.Pixel2Cell(self.mouse["begin"])
  691. pointOnLine = self.digit.GetDisplay().SelectLineByPoint(pos1)["point"]
  692. if not pointOnLine:
  693. return
  694. if self.toolbar.GetAction() in ["splitLine", "addVertex"]:
  695. self.UpdateMap(render=False) # highlight object
  696. self.DrawCross(
  697. pdc=self.pdcTmp,
  698. coords=self.Cell2Pixel((pointOnLine[0], pointOnLine[1])),
  699. size=5,
  700. )
  701. else: # removeVertex
  702. # get only id of vertex
  703. try:
  704. id = self.digit.GetDisplay().GetSelectedVertex(pos1)[0]
  705. except IndexError:
  706. id = None
  707. if id:
  708. x, y = self.pdcVector.GetIdBounds(id)[0:2]
  709. self.pdcVector.RemoveId(id)
  710. self.UpdateMap(render=False) # highlight object
  711. self.DrawCross(pdc=self.pdcTmp, coords=(x, y), size=5)
  712. else:
  713. # unselect
  714. self.digit.GetDisplay().SetSelected([])
  715. self.UpdateMap(render=False)
  716. def OnLeftUpCopyLine(self, event):
  717. """Left mouse button released - vector digitizer copy feature
  718. action
  719. """
  720. pos1 = self.Pixel2Cell(self.mouse["begin"])
  721. pos2 = self.Pixel2Cell(self.mouse["end"])
  722. if (
  723. UserSettings.Get(
  724. group="vdigit", key="bgmap", subkey="value", settings_type="internal"
  725. )
  726. == ""
  727. ):
  728. # no background map -> copy from current vector map layer
  729. nselected = self.digit.GetDisplay().SelectLinesByBox((pos1, pos2))
  730. if nselected > 0:
  731. # highlight selected features
  732. self.UpdateMap(render=False)
  733. else:
  734. self.UpdateMap(render=False, renderVector=False)
  735. else:
  736. # copy features from background map
  737. self.copyIds = self.digit.SelectLinesFromBackgroundMap(bbox=(pos1, pos2))
  738. if len(self.copyIds) > 0:
  739. color = UserSettings.Get(
  740. group="vdigit", key="symbol", subkey=["highlight", "color"]
  741. )
  742. colorStr = str(color[0]) + ":" + str(color[1]) + ":" + str(color[2])
  743. dVectTmp = [
  744. "d.vect",
  745. "map=%s"
  746. % UserSettings.Get(
  747. group="vdigit",
  748. key="bgmap",
  749. subkey="value",
  750. settings_type="internal",
  751. ),
  752. "cats=%s" % ListOfCatsToRange(self.copyIds),
  753. "-i",
  754. "color=%s" % colorStr,
  755. "fill_color=%s" % colorStr,
  756. "type=point,line,boundary,centroid",
  757. "width=2",
  758. ]
  759. if not self.layerTmp:
  760. self.layerTmp = self.Map.AddLayer(
  761. ltype="vector", name=QUERYLAYER, command=dVectTmp
  762. )
  763. else:
  764. self.layerTmp.SetCmd(dVectTmp)
  765. else:
  766. if self.layerTmp:
  767. self.Map.DeleteLayer(self.layerTmp)
  768. self.layerTmp = None
  769. self.UpdateMap(render=True, renderVector=True)
  770. def OnLeftUpBulkLine(self, event):
  771. """Left mouse button released - vector digitizer z-bulk line
  772. action
  773. """
  774. # select lines to be labeled
  775. pos1 = self.polycoords[0]
  776. pos2 = self.polycoords[1]
  777. nselected = self.digit.GetDisplay().SelectLinesByBox((pos1, pos2))
  778. if nselected > 0:
  779. # highlight selected features
  780. self.UpdateMap(render=False)
  781. self.DrawLines(pdc=self.pdcTmp) # redraw temp line
  782. else:
  783. self.UpdateMap(render=False, renderVector=False)
  784. def OnLeftUpConnectLine(self, event):
  785. """Left mouse button released - vector digitizer connect line
  786. action
  787. """
  788. if len(self.digit.GetDisplay().GetSelected()) > 0:
  789. self.UpdateMap(render=False)
  790. def _onLeftUp(self, event):
  791. """Left mouse button released"""
  792. if event.ControlDown():
  793. return
  794. if hasattr(self, "moveInfo"):
  795. if len(self.digit.GetDisplay().GetSelected()) == 0:
  796. self.moveInfo["begin"] = self.Pixel2Cell(
  797. self.mouse["begin"]
  798. ) # left down
  799. # eliminate initial mouse moving efect
  800. self.mouse["begin"] = self.mouse["end"]
  801. action = self.toolbar.GetAction()
  802. if action in (
  803. "deleteLine",
  804. "deleteArea",
  805. "moveLine",
  806. "moveVertex",
  807. "copyCats",
  808. "copyAttrs",
  809. "editLine",
  810. "flipLine",
  811. "mergeLine",
  812. "snapLine",
  813. "queryLine",
  814. "breakLine",
  815. "typeConv",
  816. "connectLine",
  817. ):
  818. self.OnLeftUpVarious(event)
  819. elif action in ("splitLine", "addVertex", "removeVertex"):
  820. self.OnLeftUpModifyLine(event)
  821. elif action == "copyLine":
  822. self.OnLeftUpCopyLine(event)
  823. elif action == "zbulkLine" and len(self.polycoords) == 2:
  824. self.OnLeftUpBulkLine(event)
  825. elif action == "connectLine":
  826. self.OnLeftUpConnectLine(event)
  827. if len(self.digit.GetDisplay().GetSelected()) > 0:
  828. self.redrawAll = None
  829. def _onRightDown(self, event):
  830. # digitization tool (confirm action)
  831. action = self.toolbar.GetAction()
  832. if action in ("moveLine", "moveVertex") and hasattr(self, "moveInfo"):
  833. pFrom = self.moveInfo["begin"]
  834. pTo = self.Pixel2Cell(event.GetPosition())
  835. move = (pTo[0] - pFrom[0], pTo[1] - pFrom[1])
  836. if action == "moveLine":
  837. # move line
  838. if self.digit.MoveSelectedLines(move) < 0:
  839. return
  840. elif action == "moveVertex":
  841. # move vertex
  842. fid = self.digit.MoveSelectedVertex(pFrom, move)
  843. if fid < 0:
  844. return
  845. self._geomAttrbUpdate(
  846. [
  847. fid,
  848. ]
  849. )
  850. del self.moveInfo
  851. def _onRightUp(self, event):
  852. """Right mouse button released (confirm action)"""
  853. action = self.toolbar.GetAction()
  854. if action == "addLine" and self.toolbar.GetAction("type") in [
  855. "line",
  856. "boundary",
  857. "area",
  858. ]:
  859. # -> add new line / boundary
  860. try:
  861. mapName = self.toolbar.GetLayer().GetName()
  862. except:
  863. mapName = None
  864. GError(parent=self, message=_("No vector map selected for editing."))
  865. if mapName:
  866. if self.toolbar.GetAction("type") == "line":
  867. line = True
  868. else:
  869. line = False
  870. if len(self.polycoords) < 2: # ignore 'one-point' lines
  871. return
  872. nfeat, fids = self.digit.AddFeature(
  873. self.toolbar.GetAction("type"), self.polycoords
  874. )
  875. if nfeat < 0:
  876. return
  877. position = self.Cell2Pixel(self.polycoords[-1])
  878. self.polycoords = []
  879. self.UpdateMap(render=False)
  880. self.redrawAll = True
  881. self.Refresh()
  882. # add new record into attribute table
  883. if self._addRecord() and (line is True or (not line and nfeat > 0)):
  884. posWindow = self.ClientToScreen(
  885. (
  886. position[0] + self.dialogOffset,
  887. position[1] + self.dialogOffset,
  888. )
  889. )
  890. # select attributes based on layer and category
  891. cats = {
  892. fids[0]: {
  893. UserSettings.Get(
  894. group="vdigit", key="layer", subkey="value"
  895. ): (
  896. UserSettings.Get(
  897. group="vdigit", key="category", subkey="value"
  898. ),
  899. )
  900. }
  901. }
  902. addRecordDlg = DisplayAttributesDialog(
  903. parent=self,
  904. map=mapName,
  905. cats=cats,
  906. pos=posWindow,
  907. action="add",
  908. ignoreError=True,
  909. )
  910. for fid in fids:
  911. self._geomAttrb(fid, addRecordDlg, "length")
  912. # auto-placing centroid
  913. self._geomAttrb(fid, addRecordDlg, "area")
  914. self._geomAttrb(fid, addRecordDlg, "perimeter")
  915. if addRecordDlg.IsFound():
  916. addRecordDlg.ShowModal()
  917. addRecordDlg.Destroy()
  918. elif action == "deleteLine":
  919. # -> delete selected vector features
  920. if self.digit.DeleteSelectedLines() < 0:
  921. return
  922. self._updateATM()
  923. elif action == "deleteArea":
  924. # -> delete selected vector areas
  925. if self.digit.DeleteSelectedAreas() < 0:
  926. return
  927. self._updateATM()
  928. elif action == "splitLine":
  929. # split line
  930. if self.digit.SplitLine(self.Pixel2Cell(self.mouse["begin"])) < 0:
  931. return
  932. elif action == "addVertex":
  933. # add vertex
  934. fid = self.digit.AddVertex(self.Pixel2Cell(self.mouse["begin"]))
  935. if fid < 0:
  936. return
  937. elif action == "removeVertex":
  938. # remove vertex
  939. fid = self.digit.RemoveVertex(self.Pixel2Cell(self.mouse["begin"]))
  940. if fid < 0:
  941. return
  942. self._geomAttrbUpdate(
  943. [
  944. fid,
  945. ]
  946. )
  947. elif action in ("copyCats", "copyAttrs"):
  948. if action == "copyCats":
  949. if (
  950. self.digit.CopyCats(
  951. self.copyCatsList, self.copyCatsIds, copyAttrb=False
  952. )
  953. < 0
  954. ):
  955. return
  956. else:
  957. if (
  958. self.digit.CopyCats(
  959. self.copyCatsList, self.copyCatsIds, copyAttrb=True
  960. )
  961. < 0
  962. ):
  963. return
  964. del self.copyCatsList
  965. del self.copyCatsIds
  966. self._updateATM()
  967. elif action == "editLine" and hasattr(self, "moveInfo"):
  968. line = self.digit.GetDisplay().GetSelected()[0]
  969. if self.digit.EditLine(line, self.polycoords) < 0:
  970. return
  971. del self.moveInfo
  972. elif action == "flipLine":
  973. if self.digit.FlipLine() < 0:
  974. return
  975. elif action == "mergeLine":
  976. if self.digit.MergeLine() < 0:
  977. return
  978. elif action == "breakLine":
  979. if self.digit.BreakLine() < 0:
  980. return
  981. elif action == "snapLine":
  982. if self.digit.SnapLine() < 0:
  983. return
  984. elif action == "connectLine":
  985. if len(self.digit.GetDisplay().GetSelected()) > 1:
  986. if self.digit.ConnectLine() < 0:
  987. return
  988. elif action == "copyLine":
  989. if self.digit.CopyLine(self.copyIds) < 0:
  990. return
  991. del self.copyIds
  992. if self.layerTmp:
  993. self.Map.DeleteLayer(self.layerTmp)
  994. self.UpdateMap(render=True, renderVector=False)
  995. del self.layerTmp
  996. elif action == "zbulkLine" and len(self.polycoords) == 2:
  997. pos1 = self.polycoords[0]
  998. pos2 = self.polycoords[1]
  999. selected = self.digit.GetDisplay().GetSelected()
  1000. dlg = VDigitZBulkDialog(
  1001. parent=self, title=_("Z bulk-labeling dialog"), nselected=len(selected)
  1002. )
  1003. if dlg.ShowModal() == wx.ID_OK:
  1004. if (
  1005. self.digit.ZBulkLines(
  1006. pos1, pos2, dlg.value.GetValue(), dlg.step.GetValue()
  1007. )
  1008. < 0
  1009. ):
  1010. return
  1011. self.UpdateMap(render=False)
  1012. elif action == "typeConv":
  1013. # -> feature type conversion
  1014. # - point <-> centroid
  1015. # - line <-> boundary
  1016. if self.digit.TypeConvForSelectedLines() < 0:
  1017. return
  1018. if action != "addLine":
  1019. # unselect and re-render
  1020. self.digit.GetDisplay().SetSelected([])
  1021. self.polycoords = []
  1022. self.UpdateMap(render=False)
  1023. def _onMouseMoving(self, event):
  1024. self.mouse["end"] = event.GetPosition()
  1025. Debug.msg(
  1026. 5,
  1027. "VDigitWindow.OnMouseMoving(): coords=%f,%f"
  1028. % (self.mouse["end"][0], self.mouse["end"][1]),
  1029. )
  1030. action = self.toolbar.GetAction()
  1031. if action == "addLine" and self.toolbar.GetAction("type") in [
  1032. "line",
  1033. "boundary",
  1034. "area",
  1035. ]:
  1036. if len(self.polycoords) > 0:
  1037. self.MouseDraw(
  1038. pdc=self.pdcTmp, begin=self.Cell2Pixel(self.polycoords[-1])
  1039. )
  1040. elif action in ["moveLine", "moveVertex", "editLine"] and hasattr(
  1041. self, "moveInfo"
  1042. ):
  1043. dx = self.mouse["end"][0] - self.mouse["begin"][0]
  1044. dy = self.mouse["end"][1] - self.mouse["begin"][1]
  1045. # draw lines on new position
  1046. if action == "moveLine" and len(self.moveInfo["id"]) > 0:
  1047. # move line
  1048. for id in self.moveInfo["id"]:
  1049. self.pdcTmp.TranslateId(id, dx, dy)
  1050. elif action in ["moveVertex", "editLine"]:
  1051. # move vertex ->
  1052. # (vertex, left vertex, left line,
  1053. # right vertex, right line)
  1054. # do not draw static lines
  1055. if action == "moveVertex" and len(self.moveInfo["id"]) > 0:
  1056. self.polycoords = []
  1057. self.pdcTmp.RemoveId(self.moveInfo["id"][0])
  1058. if self.moveInfo["id"][1] > 0: # previous vertex
  1059. x, y = self.Pixel2Cell(
  1060. self.pdcTmp.GetIdBounds(self.moveInfo["id"][1])[0:2]
  1061. )
  1062. self.pdcTmp.RemoveId(self.moveInfo["id"][1] + 1)
  1063. self.polycoords.append((x, y))
  1064. self.polycoords.append(self.Pixel2Cell(self.mouse["end"]))
  1065. if self.moveInfo["id"][2] > 0: # next vertex
  1066. x, y = self.Pixel2Cell(
  1067. self.pdcTmp.GetIdBounds(self.moveInfo["id"][2])[0:2]
  1068. )
  1069. self.pdcTmp.RemoveId(self.moveInfo["id"][2] - 1)
  1070. self.polycoords.append((x, y))
  1071. self.ClearLines(pdc=self.pdcTmp)
  1072. self.DrawLines(pdc=self.pdcTmp)
  1073. if action == "editLine":
  1074. self.MouseDraw(
  1075. pdc=self.pdcTmp, begin=self.Cell2Pixel(self.polycoords[-1])
  1076. )
  1077. self.Refresh() # TODO: use RefreshRect()
  1078. self.mouse["begin"] = self.mouse["end"]
  1079. elif action == "zbulkLine":
  1080. if len(self.polycoords) == 1:
  1081. # draw mouse moving
  1082. self.MouseDraw(self.pdcTmp)
  1083. def _zoom(self, event):
  1084. tmp1 = self.mouse["end"]
  1085. tmp2 = self.Cell2Pixel(self.moveInfo["begin"])
  1086. dx = tmp1[0] - tmp2[0]
  1087. dy = tmp1[1] - tmp2[1]
  1088. self.moveInfo["beginDiff"] = (dx, dy)
  1089. for id in self.moveInfo["id"]:
  1090. self.pdcTmp.RemoveId(id)
  1091. def _addRecord(self):
  1092. return UserSettings.Get(group="vdigit", key="addRecord", subkey="enabled")