wxvdigit.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. """!
  2. @package wxvdigit.py
  3. @brief wxGUI vector digitizer (base class)
  4. Code based on wxVdigit C++ component from GRASS 6.4.0. Converted to
  5. Python in 2010/12-2011/01.
  6. List of classes:
  7. - IVDigit
  8. (C) 2007-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 Martin Landa <landa.martin gmail.com>
  12. """
  13. from debug import Debug
  14. from preferences import globalSettings as UserSettings
  15. from wxvdriver import DisplayDriver
  16. from grass.lib.grass import *
  17. from grass.lib.vector import *
  18. class IVDigit:
  19. def __init__(self, mapwindow):
  20. self.map = None
  21. self.mapWindow = mapwindow
  22. if not mapwindow.parent.IsStandalone():
  23. self.log = mapwindow.parent.GetLayerManager().goutput.cmd_stderr
  24. else:
  25. self.log = sys.stderr
  26. self.toolbar = mapwindow.parent.toolbars['vdigit']
  27. self._display = DisplayDriver(device = mapwindow.pdcVector,
  28. deviceTmp = mapwindow.pdcTmp,
  29. mapObj = mapwindow.Map,
  30. log = self.log)
  31. # self.SetCategory()
  32. # layer / max category
  33. self.cats = dict()
  34. # settings
  35. self._settings = {
  36. 'breakLines' : None,
  37. 'addCentroid' : None,
  38. 'catBoundary' : None
  39. }
  40. # undo/redo
  41. self.changesets = dict()
  42. self.changesetCurrent = None # first changeset to apply
  43. self.changesetEnd = None # last changeset to be applied
  44. if self._display.mapInfo:
  45. self.InitCats()
  46. # initial value for undo/redo
  47. self.changesetEnd = self.changesetCurrent = -1
  48. def __del__(self):
  49. pass # free changesets ?
  50. def _setCategory(self):
  51. pass
  52. def _openBackgroundVectorMap(self):
  53. pass
  54. def _breakLineAtIntersection(self):
  55. pass
  56. def _addActionsBefore(self):
  57. """!Register action before operation
  58. @return changeset id
  59. """
  60. pass
  61. def _addActionsAfter(self):
  62. pass
  63. def _addActionToChangeset(self):
  64. pass
  65. def _applyChangeset(self):
  66. pass
  67. def _freeChangeset(self):
  68. pass
  69. def _removeActionFromChangeset(self):
  70. pass
  71. def AddPoint (self, map, point, x, y, z=None):
  72. """!Add new point/centroid
  73. @param map map name (unused, for compatability with VEdit)
  74. @param point feature type (if true point otherwise centroid)
  75. @param x,y,z coordinates
  76. """
  77. if UserSettings.Get(group='vdigit', key="categoryMode", subkey='selection') == 2:
  78. layer = -1 # -> no category
  79. cat = -1
  80. else:
  81. layer = UserSettings.Get(group='vdigit', key="layer", subkey='value')
  82. cat = self.SetCategory()
  83. if point:
  84. type = wxvdigit.GV_POINT
  85. else:
  86. type = wxvdigit.GV_CENTROID
  87. snap, thresh = self.__getSnapThreshold()
  88. bgmap = str(UserSettings.Get(group='vdigit', key="bgmap",
  89. subkey='value', internal=True))
  90. if z:
  91. ret = self.digit.AddLine(type, [x, y, z], layer, cat,
  92. bgmap, snap, thresh)
  93. else:
  94. ret = self.digit.AddLine(type, [x, y], layer, cat,
  95. bgmap, snap, thresh)
  96. self.toolbar.EnableUndo()
  97. return ret
  98. def AddLine (self, map, line, coords):
  99. """!Add line/boundary
  100. @param map map name (unused, for compatability with VEdit)
  101. @param line feature type (if True line, otherwise boundary)
  102. @param coords list of coordinates
  103. """
  104. if len(coords) < 2:
  105. return
  106. if UserSettings.Get(group='vdigit', key="categoryMode", subkey='selection') == 2:
  107. layer = -1 # -> no category
  108. cat = -1
  109. else:
  110. layer = UserSettings.Get(group='vdigit', key="layer", subkey='value')
  111. cat = self.SetCategory()
  112. if line:
  113. type = wxvdigit.GV_LINE
  114. else:
  115. type = wxvdigit.GV_BOUNDARY
  116. listCoords = []
  117. for c in coords:
  118. for x in c:
  119. listCoords.append(x)
  120. snap, thresh = self.__getSnapThreshold()
  121. bgmap = str(UserSettings.Get(group='vdigit', key="bgmap",
  122. subkey='value', internal=True))
  123. ret = self.digit.AddLine(type, listCoords, layer, cat,
  124. bgmap, snap, thresh)
  125. self.toolbar.EnableUndo()
  126. return ret
  127. def DeleteSelectedLines(self):
  128. """!Delete selected features
  129. @return number of deleted lines
  130. """
  131. nlines = self.digit.DeleteLines(UserSettings.Get(group='vdigit', key='delRecord', subkey='enabled'))
  132. if nlines > 0:
  133. self.toolbar.EnableUndo()
  134. return nlines
  135. def MoveSelectedLines(self, move):
  136. """!Move selected features
  137. @param move direction (x, y)
  138. """
  139. snap, thresh = self.__getSnapThreshold()
  140. bgmap = str(UserSettings.Get(group='vdigit', key="bgmap",
  141. subkey='value', internal=True))
  142. try:
  143. nlines = self.digit.MoveLines(move[0], move[1], 0.0, # TODO 3D
  144. bgmap, snap, thresh)
  145. except SystemExit:
  146. pass
  147. if nlines > 0:
  148. self.toolbar.EnableUndo()
  149. return nlines
  150. def MoveSelectedVertex(self, coords, move):
  151. """!Move selected vertex of the line
  152. @param coords click coordinates
  153. @param move X,Y direction
  154. @return id of new feature
  155. @return 0 vertex not moved (not found, line is not selected)
  156. """
  157. snap, thresh = self.__getSnapThreshold()
  158. bgmap = str(UserSettings.Get(group='vdigit', key="bgmap",
  159. subkey='value', internal=True))
  160. moved = self.digit.MoveVertex(coords[0], coords[1], 0.0, # TODO 3D
  161. move[0], move[1], 0.0,
  162. bgmap, snap,
  163. self.driver.GetThreshold(type='selectThresh'), thresh)
  164. if moved:
  165. self.toolbar.EnableUndo()
  166. return moved
  167. def AddVertex(self, coords):
  168. """!Add new vertex to the selected line/boundary on position 'coords'
  169. @param coords coordinates to add vertex
  170. @return id of new feature
  171. @return 0 nothing changed
  172. @return -1 on failure
  173. """
  174. added = self.digit.ModifyLineVertex(1, coords[0], coords[1], 0.0, # TODO 3D
  175. self.driver.GetThreshold(type='selectThresh'))
  176. if added > 0:
  177. self.toolbar.EnableUndo()
  178. return added
  179. def RemoveVertex(self, coords):
  180. """!Remove vertex from the selected line/boundary on position 'coords'
  181. @param coords coordinates to remove vertex
  182. @return id of new feature
  183. @return 0 nothing changed
  184. @return -1 on failure
  185. """
  186. deleted = self.digit.ModifyLineVertex(0, coords[0], coords[1], 0.0, # TODO 3D
  187. self.driver.GetThreshold(type='selectThresh'))
  188. if deleted > 0:
  189. self.toolbar.EnableUndo()
  190. return deleted
  191. def SplitLine(self, coords):
  192. """!Split selected line/boundary on position 'coords'
  193. @param coords coordinates to split line
  194. @return 1 line modified
  195. @return 0 nothing changed
  196. @return -1 error
  197. """
  198. ret = self.digit.SplitLine(coords[0], coords[1], 0.0, # TODO 3D
  199. self.driver.GetThreshold('selectThresh'))
  200. if ret > 0:
  201. self.toolbar.EnableUndo()
  202. return ret
  203. def EditLine(self, line, coords):
  204. """!Edit existing line/boundary
  205. @param line id of line to be modified
  206. @param coords list of coordinates of modified line
  207. @return feature id of new line
  208. @return -1 on error
  209. """
  210. try:
  211. lineid = line[0]
  212. except:
  213. lineid = -1
  214. if len(coords) < 2:
  215. self.DeleteSelectedLines()
  216. return 0
  217. listCoords = []
  218. for c in coords:
  219. for x in c:
  220. listCoords.append(x)
  221. snap, thresh = self.__getSnapThreshold()
  222. bgmap = str(UserSettings.Get(group='vdigit', key="bgmap",
  223. subkey='value', internal=True))
  224. try:
  225. ret = self.digit.RewriteLine(lineid, listCoords,
  226. bgmap, snap, thresh)
  227. except SystemExit:
  228. pass
  229. if ret > 0:
  230. self.toolbar.EnableUndo()
  231. return ret
  232. def FlipLine(self):
  233. """!Flip selected lines/boundaries
  234. @return number of modified lines
  235. @return -1 on error
  236. """
  237. ret = self.digit.FlipLines()
  238. if ret > 0:
  239. self.toolbar.EnableUndo()
  240. return ret
  241. def MergeLine(self):
  242. """!Merge selected lines/boundaries
  243. @return number of modified lines
  244. @return -1 on error
  245. """
  246. ret = self.digit.MergeLines()
  247. if ret > 0:
  248. self.toolbar.EnableUndo()
  249. return ret
  250. def BreakLine(self):
  251. """!Break selected lines/boundaries
  252. @return number of modified lines
  253. @return -1 on error
  254. """
  255. ret = self.digit.BreakLines()
  256. if ret > 0:
  257. self.toolbar.EnableUndo()
  258. return ret
  259. def SnapLine(self):
  260. """!Snap selected lines/boundaries
  261. @return on success
  262. @return -1 on error
  263. """
  264. snap, thresh = self.__getSnapThreshold()
  265. ret = self.digit.SnapLines(thresh)
  266. if ret == 0:
  267. self.toolbar.EnableUndo()
  268. return ret
  269. def ConnectLine(self):
  270. """!Connect selected lines/boundaries
  271. @return 1 lines connected
  272. @return 0 lines not connected
  273. @return -1 on error
  274. """
  275. snap, thresh = self.__getSnapThreshold()
  276. ret = self.digit.ConnectLines(thresh)
  277. if ret > 0:
  278. self.toolbar.EnableUndo()
  279. return ret
  280. def CopyLine(self, ids=[]):
  281. """!Copy features from (background) vector map
  282. @param ids list of line ids to be copied
  283. @return number of copied features
  284. @return -1 on error
  285. """
  286. bgmap = str(UserSettings.Get(group='vdigit', key='bgmap',
  287. subkey='value', internal=True))
  288. if len(bgmap) > 0:
  289. ret = self.digit.CopyLines(ids, bgmap)
  290. else:
  291. ret = self.digit.CopyLines(ids, None)
  292. if ret > 0:
  293. self.toolbar.EnableUndo()
  294. return ret
  295. def CopyCats(self, fromId, toId, copyAttrb=False):
  296. """!Copy given categories to objects with id listed in ids
  297. @param cats ids of 'from' feature
  298. @param ids ids of 'to' feature(s)
  299. @return number of modified features
  300. @return -1 on error
  301. """
  302. if len(fromId) == 0 or len(toId) == 0:
  303. return 0
  304. ret = self.digit.CopyCats(fromId, toId, copyAttrb)
  305. if ret > 0:
  306. self.toolbar.EnableUndo()
  307. return ret
  308. def SelectLinesByQuery(self, pos1, pos2):
  309. """!Select features by query
  310. @param pos1, pos2 bounding box definition
  311. """
  312. thresh = self.SelectLinesByQueryThresh()
  313. w, n = pos1
  314. e, s = pos2
  315. query = wxvdigit.QUERY_UNKNOWN
  316. if UserSettings.Get(group='vdigit', key='query', subkey='selection') == 0:
  317. query = wxvdigit.QUERY_LENGTH
  318. else:
  319. query = wxvdigit.QUERY_DANGLE
  320. type = wxvdigit.GV_POINTS | wxvdigit.GV_LINES # TODO: 3D
  321. ids = self.digit.SelectLinesByQuery(w, n, 0.0, e, s, 1000.0,
  322. UserSettings.Get(group='vdigit', key='query', subkey='box'),
  323. query, type, thresh)
  324. Debug.msg(4, "VDigit.SelectLinesByQuery(): %s" % \
  325. ",".join(["%d" % v for v in ids]))
  326. return ids
  327. def GetLineCats(self, line=-1):
  328. """!Get layer/category pairs from given (selected) line
  329. @param line feature id (-1 for first selected line)
  330. """
  331. return dict(self.digit.GetLineCats(line))
  332. def GetLineLength(self, line):
  333. """!Get line length
  334. @param line feature id
  335. @return line length
  336. @return -1 on error
  337. """
  338. return self.digit.GetLineLength(line)
  339. def GetAreaSize(self, centroid):
  340. """!Get area size
  341. @param centroid centroid id
  342. @return area size
  343. @return -1 on error
  344. """
  345. return self.digit.GetAreaSize(centroid)
  346. def GetAreaPerimeter(self, centroid):
  347. """!Get area perimeter
  348. @param centroid centroid id
  349. @return area size
  350. @return -1 on error
  351. """
  352. return self.digit.GetAreaPerimeter(centroid)
  353. def SetLineCats(self, line, layer, cats, add=True):
  354. """!Set categories for given line and layer
  355. @param line feature id
  356. @param layer layer number (-1 for first selected line)
  357. @param cats list of categories
  358. @param add if True to add, otherwise do delete categories
  359. @return new feature id (feature need to be rewritten)
  360. @return -1 on error
  361. """
  362. ret = self.digit.SetLineCats(line, layer, cats, add)
  363. if ret > 0:
  364. self.toolbar.EnableUndo()
  365. return ret
  366. def GetLayers(self):
  367. """!Get list of layers"""
  368. return self.digit.GetLayers()
  369. def TypeConvForSelectedLines(self):
  370. """!Feature type conversion for selected objects.
  371. Supported conversions:
  372. - point <-> centroid
  373. - line <-> boundary
  374. @return number of modified features
  375. @return -1 on error
  376. """
  377. ret = self.digit.TypeConvLines()
  378. if ret > 0:
  379. self.toolbar.EnableUndo()
  380. return ret
  381. def Undo(self, level=-1):
  382. """!Undo action
  383. @param level levels to undo (0 to revert all)
  384. @return id of current changeset
  385. """
  386. try:
  387. ret = self.digit.Undo(level)
  388. except SystemExit:
  389. ret = -2
  390. if ret == -2:
  391. raise gcmd.GException(_("Undo failed, data corrupted."))
  392. self.mapWindow.UpdateMap(render=False)
  393. if ret < 0: # disable undo tool
  394. self.toolbar.EnableUndo(False)
  395. def ZBulkLines(self, pos1, pos2, start, step):
  396. """!Z-bulk labeling
  397. @param pos1 reference line (start point)
  398. @param pos1 reference line (end point)
  399. @param start starting value
  400. @param step step value
  401. @return number of modified lines
  402. @return -1 on error
  403. """
  404. ret = self.digit.ZBulkLabeling(pos1[0], pos1[1], pos2[0], pos2[1],
  405. start, step)
  406. if ret > 0:
  407. self.toolbar.EnableUndo()
  408. return ret
  409. def __getSnapThreshold(self):
  410. """!Get snap mode and threshold value
  411. @return (snap, thresh)
  412. """
  413. thresh = self.driver.GetThreshold()
  414. if thresh > 0.0:
  415. if UserSettings.Get(group='vdigit', key='snapToVertex', subkey='enabled') is True:
  416. snap = wxvdigit.SNAPVERTEX
  417. else:
  418. snap = wxvdigit.SNAP
  419. else:
  420. snap = wxvdigit.NO_SNAP
  421. return (snap, thresh)
  422. def GetDisplay(self):
  423. """!Get display driver instance"""
  424. return self._display
  425. def OpenMap(self, name):
  426. """!Open vector map for editing
  427. @param map name of vector map to be set up
  428. """
  429. Debug.msg (3, "AbstractDigit.SetMapName map=%s" % name)
  430. self.map = name
  431. name, mapset = name.split('@')
  432. try:
  433. ret = self._display.OpenMap(str(name), str(mapset), True)
  434. except SystemExit:
  435. ret = -1
  436. # except StandardError, e:
  437. # raise gcmd.GException(_("Unable to initialize display driver of vector "
  438. # "digitizer. See 'Command output' for details.\n\n"
  439. # "Details: ") + repr(e))
  440. # if map and ret == -1:
  441. # raise gcmd.GException(_('Unable to open vector map <%s> for editing.\n\n'
  442. # 'Data are probably corrupted, '
  443. # 'try to run v.build to rebuild '
  444. # 'the topology (Vector->Develop vector map->'
  445. # 'Create/rebuild topology).') % map)
  446. # if not map and ret != 0:
  447. # raise gcmd.GException(_('Unable to open vector map <%s> for editing.\n\n'
  448. # 'Data are probably corrupted, '
  449. # 'try to run v.build to rebuild '
  450. # 'the topology (Vector->Develop vector map->'
  451. # 'Create/rebuild topology).') % map)
  452. self.InitCats()
  453. def CloseMap(self):
  454. """!Close currently open vector map
  455. """
  456. if not self.map:
  457. return
  458. self._display.CloseMap()
  459. def InitCats(self):
  460. """!Initialize categories information
  461. @return 0 on success
  462. @return -1 on error
  463. """
  464. self.cats.clear()
  465. mapInfo = self._display.mapInfo
  466. if not mapInfo:
  467. return -1
  468. ndblinks = Vect_get_num_dblinks(byref(mapInfo))
  469. for i in range(ndblinks):
  470. fi = Vect_get_dblink(byref(mapInfo), i).contents
  471. if fi:
  472. self.cats[fi.number] = None
  473. # find max category
  474. nfields = Vect_cidx_get_num_fields(byref(mapInfo))
  475. Debug.msg(2, "wxDigit.InitCats(): nfields=%d", nfields)
  476. for i in range(nfields):
  477. field = Vect_cidx_get_field_number(byref(mapInfo), i)
  478. ncats = Vect_cidx_get_num_cats_by_index(byref(mapInfo), i)
  479. if field <= 0:
  480. continue
  481. for j in range(ncats):
  482. cat = c_int()
  483. type = c_int()
  484. id = c_int()
  485. Vect_cidx_get_cat_by_index(byref(mapInfo), i, j,
  486. byref(cat), byref(type), byref(id))
  487. if self.cats.has_key(field):
  488. if cat > self.cats[field]:
  489. self.cats[field] = cat.value
  490. else:
  491. self.cats[field] = cat.value
  492. Debug.msg(3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field])
  493. # set default values
  494. for field, cat in self.cats.iteritems():
  495. if cat == None:
  496. self.cats[field] = 0 # first category 1
  497. Debug.msg(3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field])
  498. def AddLine(self):
  499. pass
  500. def RewriteLine(self):
  501. pass
  502. def SplitLine(self):
  503. pass
  504. def DeleteLines(self):
  505. pass
  506. def MoveLines(self):
  507. pass
  508. def FlipLines(self):
  509. pass
  510. def MergeLines(self):
  511. pass
  512. def BreakLines(self):
  513. pass
  514. def SnapLines(self):
  515. pass
  516. def ConnectLines(self):
  517. pass
  518. def TypeConvLines(self):
  519. pass
  520. def ZBulkLabeling(self):
  521. pass
  522. def CopyLines(self):
  523. pass
  524. def MoveVertex(self):
  525. pass
  526. def ModifyLineVertex(self):
  527. pass
  528. def SelectLinesByQuery(self):
  529. pass
  530. def GetLineLength(self):
  531. pass
  532. def GetAreaSize(self):
  533. pass
  534. def GetAreaPerimeter(self):
  535. pass
  536. def CopyCats(self):
  537. pass
  538. def GetCategory(self, layer):
  539. """!Get max category number for layer
  540. @param layer layer number
  541. @return category number (0 if no category found)
  542. @return -1 on error
  543. """
  544. if cats.find(layer) != cats.end():
  545. Debug.msg(3, "vdigit.GetCategory(): layer=%d, cat=%d", layer, cats[layer])
  546. return cats[layer]
  547. return 0
  548. def GetLineCats(self):
  549. pass
  550. def SetLineCats(self):
  551. pass
  552. def GetLayers(self):
  553. pass
  554. def Undo(self):
  555. pass
  556. def GetUndoLevel(self):
  557. pass
  558. def UpdateSettings(self, breakLines, addCentroid, catBoundary):
  559. """!Update digit settings
  560. @param breakLines break lines on intersection
  561. @param addCentroid add centroid to left/right area
  562. @param catBoundary attach category to boundary
  563. """
  564. self._settings['breakLines'] = breakLines
  565. self._settings['addCentroid'] = addCentroid
  566. self._settings['catBoundary'] = None # !catBoundary # do not attach
  567. def SetCategory(self):
  568. """!Return category number to use (according Settings)"""
  569. if not UserSettings.Get(group = 'vdigit', key = 'categoryMode', subkey = 'selection'):
  570. self.SetCategoryNextToUse()
  571. return UserSettings.Get(group = 'vdigit', key = 'category', subkey = 'value')
  572. def SetCategoryNextToUse(self):
  573. """!Find maximum category number in the map layer
  574. and update Digit.settings['category']
  575. @return 'True' on success, 'False' on failure
  576. """
  577. # vector map layer without categories, reset to '1'
  578. UserSettings.Set(group = 'vdigit', key = 'category', subkey = 'value', value = 1)
  579. if self.map:
  580. cat = self.GetCategory(UserSettings.Get(group = 'vdigit', key = 'layer', subkey = 'value'))
  581. cat += 1
  582. UserSettings.Set(group = 'vdigit', key = 'category', subkey = 'value',
  583. value = cat)