wxvdigit.py 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429
  1. """!
  2. @package wxvdigit.py
  3. @brief wxGUI vector digitizer (base class)
  4. Code based on wxVdigit C++ component from GRASS 6.4.0
  5. (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.
  6. List of classes:
  7. - VDigitError
  8. - IVDigit
  9. (C) 2007-2011 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. """
  14. from gcmd import GError
  15. from debug import Debug
  16. from preferences import globalSettings as UserSettings
  17. from wxvdriver import DisplayDriver
  18. from grass.lib.grass import *
  19. from grass.lib.vector import *
  20. from grass.lib.vedit import *
  21. from grass.lib.dbmi import *
  22. class VDigitError:
  23. def __init__(self, parent):
  24. """!Class for managing error messages for vector digitizer
  25. @param parent parent window for dialogs
  26. """
  27. self.parent = parent
  28. self.caption = _('Digitization Error')
  29. def NoMap(self, name = None):
  30. """!No map for editing"""
  31. if name:
  32. message = _('Unable to open vector map <%s>.') % name
  33. else:
  34. message = _('No vector map open for editing.')
  35. GError(message + ' ' + _('Operation cancelled.'),
  36. parent = self.parent,
  37. caption = self.caption)
  38. def WriteLine(self):
  39. """!Writing line failed
  40. """
  41. GError(message = _('Writing new feature failed. '
  42. 'Operation cancelled.'),
  43. parent = self.parent,
  44. caption = self.caption)
  45. def ReadLine(self, line):
  46. """!Reading line failed
  47. """
  48. GError(message = _('Reading feature id %d failed. '
  49. 'Operation cancelled.') % line,
  50. parent = self.parent,
  51. caption = self.caption)
  52. def DbLink(self, dblink):
  53. """!No dblink available
  54. """
  55. GError(message = _('Database link %d not available. '
  56. 'Operation cancelled.') % dblink,
  57. parent = self.parent,
  58. caption = self.caption)
  59. def Driver(self, driver):
  60. """!Staring driver failed
  61. """
  62. GError(message = _('Unable to start database driver <%s>. '
  63. 'Operation cancelled.') % driver,
  64. parent = self.parent,
  65. caption = self.caption)
  66. def Database(self, driver, database):
  67. """!Opening database failed
  68. """
  69. GError(message = _('Unable to open database <%s> by driver <%s>. '
  70. 'Operation cancelled.') % (database, driver),
  71. parent = self.parent,
  72. caption = self.caption)
  73. def DbExecute(self, sql):
  74. """!Sql query failed
  75. """
  76. GError(message = _("Unable to execute SQL query '%s'. "
  77. "Operation cancelled.") % sql,
  78. parent = self.parent,
  79. caption = self.caption)
  80. def DeadLine(self, line):
  81. """!Dead line
  82. """
  83. GError(message = _("Feature id %d is marked as dead. "
  84. "Operation cancelled.") % line,
  85. parent = self.parent,
  86. caption = self.caption)
  87. class IVDigit:
  88. def __init__(self, mapwindow):
  89. """!Base class for vector digitizer (ctypes interface)
  90. @parem mapwindow reference for map window (BufferedWindow)
  91. """
  92. self.poMapInfo = None # pointer to Map_info
  93. self.mapWindow = mapwindow
  94. # background map
  95. self.bgMapInfo = Map_info()
  96. self.poBgMapInfo = self.popoBgMapInfo = None
  97. if not mapwindow.parent.IsStandalone():
  98. goutput = mapwindow.parent.GetLayerManager().GetLogWindow()
  99. log = goutput.GetLog(err = True)
  100. progress = goutput.GetProgressBar()
  101. else:
  102. log = sys.stderr
  103. progress = None
  104. self.toolbar = mapwindow.parent.toolbars['vdigit']
  105. self._error = VDigitError(parent = self.mapWindow)
  106. self._display = DisplayDriver(device = mapwindow.pdcVector,
  107. deviceTmp = mapwindow.pdcTmp,
  108. mapObj = mapwindow.Map,
  109. window = mapwindow,
  110. glog = log,
  111. gprogress = progress)
  112. # GRASS lib
  113. self.poPoints = Vect_new_line_struct()
  114. self.poCats = Vect_new_cats_struct()
  115. # self.SetCategory()
  116. # layer / max category
  117. self.cats = dict()
  118. # settings
  119. self.settings = {
  120. 'breakLines' : False,
  121. 'addCentroid' : False,
  122. 'catBoundary' : True,
  123. }
  124. # undo/redo
  125. self.changesets = dict()
  126. self.changesetCurrent = -1 # first changeset to apply
  127. self.changesetEnd = -1 # last changeset to be applied
  128. if self.poMapInfo:
  129. self.InitCats()
  130. def __del__(self):
  131. Vect_destroy_line_struct(self.poPoints)
  132. self.poPoints = None
  133. Vect_destroy_cats_struct(self.poCats)
  134. self.poCats = None
  135. if self.poBgMapInfo:
  136. Vect_close(self.poBgMapInfo)
  137. self.poBgMapInfo = self.popoBgMapInfo = None
  138. del self.bgMapInfo
  139. def CloseBackgroundMap(self):
  140. """!Close background vector map"""
  141. if not self.poBgMapInfo:
  142. return
  143. Vect_close(self.poBgMapInfo)
  144. self.poBgMapInfo = self.popoBgMapInfo = None
  145. def OpenBackgroundMap(self, bgmap):
  146. """!Open background vector map
  147. @todo support more background maps then only one
  148. @param bgmap name of vector map to be opened
  149. @return pointer to map_info
  150. @return None on error
  151. """
  152. name = create_string_buffer(GNAME_MAX)
  153. mapset = create_string_buffer(GMAPSET_MAX)
  154. if not G_name_is_fully_qualified(bgmap, name, mapset):
  155. name = str(bgmap)
  156. mapset = str(G_find_vector2(bgmap, ''))
  157. else:
  158. name = str(name.value)
  159. mapset = str(mapset.value)
  160. if (name == Vect_get_name(self.poMapInfo) and \
  161. mapset == Vect_get_mapset(self.poMapInfo)):
  162. self.poBgMapInfo = self.popoBgMapInfo = None
  163. self._error.NoMap(bgmap)
  164. return
  165. self.poBgMapInfo = pointer(self.bgMapInfo)
  166. self.popoBgMapInfo = pointer(self.poBgMapInfo)
  167. if Vect_open_old(self.poBgMapInfo, name, mapset) == -1:
  168. self.poBgMapInfo = self.popoBgMapInfo = None
  169. self._error.NoMap(bgmap)
  170. return
  171. def _getSnapMode(self):
  172. """!Get snapping mode
  173. - snap to vertex
  174. - snap to nodes
  175. - no snapping
  176. @return snap mode
  177. """
  178. threshold = self._display.GetThreshold()
  179. if threshold > 0.0:
  180. if UserSettings.Get(group = 'vdigit', key = 'snapToVertex', subkey = 'enabled'):
  181. return SNAPVERTEX
  182. else:
  183. return SNAP
  184. else:
  185. return NO_SNAP
  186. def _breakLineAtIntersection(self):
  187. pass
  188. def _addActionsBefore(self):
  189. """!Register action before operation
  190. @return changeset id
  191. """
  192. changeset = len(self.changesets)
  193. for line in self._display.selected['ids']:
  194. if Vect_line_alive(self.poMapInfo, line):
  195. self._addActionToChangeset(changeset, line, add = False)
  196. return changeset
  197. def _applyChangeset(self, changeset, undo):
  198. """!Apply changeset (undo/redo changeset)
  199. @param changeset changeset id
  200. @param undo True for undo otherwise redo
  201. @return 1 changeset applied
  202. @return 0 changeset not applied
  203. @return -1 on error
  204. """
  205. if changeset < 0 or changeset > len(self.changesets.keys()):
  206. return -1
  207. if self.changesetEnd < 0:
  208. self.changesetEnd = changeset
  209. ret = 0
  210. actions = self.changesets[changeset]
  211. for action in actions:
  212. add = action['add']
  213. line = action['line']
  214. if (undo and add) or \
  215. (not undo and not add):
  216. if Vect_line_alive(self.poMapInfo, line):
  217. Debug.msg(3, "IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted",
  218. changeset, line)
  219. Vect_delete_line(self.poMapInfo, line)
  220. ret = 1
  221. else:
  222. Debug.msg(3, "Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead",
  223. changeset, line)
  224. else: # delete
  225. offset = action['offset']
  226. if not Vect_line_alive(self.poMapInfo, line):
  227. Debug.msg(3, "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added",
  228. changeset, line)
  229. if Vect_restore_line(self.poMapInfo, line, offset) < 0:
  230. return -1
  231. ret = 1
  232. else:
  233. Debug.msg(3, "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive",
  234. changeset, line)
  235. return ret
  236. def _addActionsAfter(self, changeset, nlines):
  237. """!Register action after operation
  238. @param changeset changeset id
  239. @param nline number of lines
  240. """
  241. for line in self._display.selected['ids']:
  242. if Vect_line_alive(self.poMapInfo, line):
  243. self._removeActionFromChangeset(changeset, line, add = False)
  244. for line in range(nlines + 1, Vect_get_num_lines(self.poMapInfo)):
  245. if Vect_line_alive(self.poMapInfo, line):
  246. self._addActionToChangeset(changeset, line, add = True)
  247. def _addActionToChangeset(self, changeset, line, add):
  248. """!Add action to changeset
  249. @param changeset id of changeset
  250. @param line feature id
  251. @param add True to add, otherwise delete
  252. """
  253. if not self._checkMap():
  254. return
  255. if not Vect_line_alive(self.poMapInfo, line):
  256. return
  257. offset = Vect_get_line_offset(self.poMapInfo, line)
  258. if not self.changesets.has_key(changeset):
  259. self.changesets[changeset] = list()
  260. self.changesetCurrent = changeset
  261. self.changesets[changeset].append({ 'add' : add,
  262. 'line' : line,
  263. 'offset' : offset })
  264. Debug.msg(3, "IVDigit._addActionToChangeset(): changeset=%d, type=%d, line=%d, offset=%d",
  265. changeset, type, line, offset)
  266. def _removeActionFromChangeset(self, changeset, line, add):
  267. """!Remove action from changeset
  268. @param changeset changeset id
  269. @param line line id
  270. @param add True for add, False for delete
  271. @return number of actions in changeset
  272. @return -1 on error
  273. """
  274. if changeset not in self.changesets.keys():
  275. return -1
  276. alist = self.changesets[changeset]
  277. for action in alist:
  278. if action['add'] == add and action['line'] == line:
  279. alist.remove(action)
  280. return len(alist)
  281. def AddFeature(self, ftype, points):
  282. """!Add new feature
  283. @param ftype feature type (point, line, centroid, boundary)
  284. @param points tuple of points ((x, y), (x, y), ...)
  285. @return new feature id
  286. """
  287. if UserSettings.Get(group = 'vdigit', key = "categoryMode", subkey = 'selection') == 2:
  288. layer = -1 # -> no category
  289. cat = -1
  290. else:
  291. layer = UserSettings.Get(group = 'vdigit', key = "layer", subkey = 'value')
  292. cat = self.cats.get(layer, 1)
  293. if ftype == 'point':
  294. vtype = GV_POINT
  295. elif ftype == 'line':
  296. vtype = GV_LINE
  297. elif ftype == 'centroid':
  298. vtype = GV_CENTROID
  299. elif ftype == 'boundary':
  300. vtype = GV_BOUNDARY
  301. else:
  302. GError(parent = self.mapWindow,
  303. message = _("Unknown feature type '%s'") % ftype)
  304. return
  305. if vtype & GV_LINES and len(points) < 2:
  306. GError(parent = self.mapWindow,
  307. message = _("Not enough points for line"))
  308. return
  309. self.toolbar.EnableUndo()
  310. return self._addFeature(vtype, points, layer, cat,
  311. self._getSnapMode(), self._display.GetThreshold())
  312. def DeleteSelectedLines(self):
  313. """!Delete selected features
  314. @return number of deleted features
  315. """
  316. deleteRec = UserSettings.Get(group = 'vdigit', key = 'delRecord', subkey = 'enabled')
  317. if not self._checkMap():
  318. return -1
  319. n_dblinks = Vect_get_num_dblinks(self.poMapInfo)
  320. Cats_del = None
  321. # collect categories for delete if requested
  322. if deleteRec:
  323. poCats = Vect_new_cats_struct()
  324. poCatsDel = Vect_new_cats_struct()
  325. for i in self._display.selected['ids']:
  326. if Vect_read_line(self.poMapInfo, None, poCats, i) < 0:
  327. Vect_destroy_cats_struct(poCatsDel)
  328. self._error.ReadLine(i)
  329. return -1
  330. cats = poCats.contents
  331. for j in range(cats.n_cats):
  332. Vect_cat_set(poCatsDel, cats.field[j], cats.cat[j])
  333. Vect_destroy_cats_struct(poCats)
  334. # register changeset
  335. changeset = self._addActionsBefore()
  336. poList = self._display.GetSelectedIList()
  337. nlines = Vedit_delete_lines(self.poMapInfo, poList)
  338. Vect_destroy_list(poList)
  339. self._display.selected['ids'] = list()
  340. if nlines > 0 and deleteRec:
  341. handle = dbHandle()
  342. poHandle = pointer(handle)
  343. stmt = dbString()
  344. poStmt = pointer(stmt)
  345. for dblink in range(n_dblinks):
  346. poFi = Vect_get_dblink(self.poMapInfo, dblink)
  347. if poFi is None:
  348. self._error.DbLink(dblink)
  349. return -1
  350. Fi = poFi.contents
  351. poDriver = db_start_driver(Fi.driver)
  352. if poDriver is None:
  353. self._error.Driver(Fi.driver)
  354. return -1
  355. db_init_handle(poHandle)
  356. db_set_handle(poHandle, Fi.database, None)
  357. if db_open_database(poDriver, poHandle) != DB_OK:
  358. self._error.Database(Fi.driver, Fi.database)
  359. return -1
  360. db_init_string(poStmt)
  361. db_set_string(poStmt, "DELETE FROM %s WHERE" % Fi.table)
  362. n_cats = 0;
  363. catsDel = poCatsDel.contents
  364. for c in range(catsDel.n_cats):
  365. if catsDel.field[c] == Fi.number:
  366. if n_cats > 0:
  367. db_append_string(poStmt, " or")
  368. db_append_string(poStmt, " %s = %d" % (Fi.key, catsDel.cat[c]))
  369. n_cats += 1
  370. Vect_cat_del(poCatsDel, Fi.number)
  371. if n_cats and \
  372. db_execute_immediate(poDriver, poStmt) != DB_OK:
  373. self._error.DbExecute(db_get_string(poStmt))
  374. return -1
  375. db_close_database(poDriver)
  376. db_shutdown_driver(poDriver)
  377. if poCatsDel:
  378. Vect_destroy_cats_struct(poCatsDel)
  379. if nlines > 0:
  380. self.toolbar.EnableUndo()
  381. return nlines
  382. def MoveSelectedLines(self, move):
  383. """!Move selected features
  384. @param move direction (x, y)
  385. """
  386. if not self._checkMap():
  387. return -1
  388. thresh = self._display.GetThreshold()
  389. snap = self._getSnapMode()
  390. nlines = Vect_get_num_lines(self.poMapInfo)
  391. # register changeset
  392. changeset = self._addActionsBefore()
  393. poList = self._display.GetSelectedIList()
  394. nlines = Vedit_move_lines(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
  395. poList,
  396. move[0], move[1], 0,
  397. snap, thresh)
  398. Vect_destroy_list(poList)
  399. if nlines > 0:
  400. self._addActionsAfter(changeset, nlines)
  401. else:
  402. del self.changesets[changeset]
  403. if nlines > 0 and self.settings['breakLines']:
  404. for i in range(1, nlines):
  405. self._breakLineAtIntersection(nlines + i, None, changeset)
  406. if nlines > 0:
  407. self.toolbar.EnableUndo()
  408. return nlines
  409. def MoveSelectedVertex(self, point, move):
  410. """!Move selected vertex of the line
  411. @param point location point
  412. @param move x,y direction
  413. @return id of new feature
  414. @return 0 vertex not moved (not found, line is not selected)
  415. @return -1 on error
  416. """
  417. if not self._checkMap():
  418. return -1
  419. if len(self._display.selected['ids']) != 1:
  420. return -1
  421. Vect_reset_line(self.poPoints)
  422. Vect_append_point(self.poPoints, point[0], point[1], 0.0)
  423. nlines = Vect_get_num_lines(self.poMapInfo)
  424. changeset = self._addActionsBefore()
  425. # move only first found vertex in bbox
  426. poList = self._display.GetSelectedIList()
  427. moved = Vedit_move_vertex(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
  428. poList, self.poPoints,
  429. self._display.GetThreshold(type = 'selectThresh'),
  430. self._display.GetThreshold(),
  431. move[0], move[1], 0.0,
  432. 1, self._getSnapMode())
  433. Vect_destroy_list(poList)
  434. if moved > 0:
  435. self._addActionsAfter(changeset, nlines)
  436. else:
  437. del self.changesets[changeset]
  438. if moved > 0 and self.settings['breakLines']:
  439. self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
  440. None, changeset)
  441. if moved > 0:
  442. self.toolbar.EnableUndo()
  443. return moved
  444. def AddVertex(self, coords):
  445. """!Add new vertex to the selected line/boundary on position 'coords'
  446. @param coords coordinates to add vertex
  447. @return id of new feature
  448. @return 0 nothing changed
  449. @return -1 on failure
  450. """
  451. added = self._ModifyLineVertex(coords, add = True)
  452. if added > 0:
  453. self.toolbar.EnableUndo()
  454. return added
  455. def RemoveVertex(self, coords):
  456. """!Remove vertex from the selected line/boundary on position 'coords'
  457. @param coords coordinates to remove vertex
  458. @return id of new feature
  459. @return 0 nothing changed
  460. @return -1 on failure
  461. """
  462. deleted = self._ModifyLineVertex(coords, add = False)
  463. if deleted > 0:
  464. self.toolbar.EnableUndo()
  465. return deleted
  466. def SplitLine(self, point):
  467. """!Split/break selected line/boundary on given position
  468. @param point point where to split line
  469. @return 1 line modified
  470. @return 0 nothing changed
  471. @return -1 error
  472. """
  473. thresh = self._display.GetThreshold('selectThresh')
  474. if not self._checkMap():
  475. return -1
  476. poList = self._display.GetSelectedIList()
  477. Vect_reset_line(self.poPoints)
  478. Vect_append_point(self.poPoints, point[0], point[1], 0.0)
  479. nlines = Vect_get_num_lines(self.poMapInfo)
  480. changeset = self._addActionsBefore()
  481. ret = Vedit_split_lines(self.poMapInfo, poList,
  482. self.poPoints, thresh, poList)
  483. Vect_destroy_list(poList)
  484. if ret > 0:
  485. self._addActionsAfter(changeset, nlines)
  486. self.toolbar.EnableUndo()
  487. else:
  488. del self.changesets[changeset]
  489. return ret
  490. def EditLine(self, line, coords):
  491. """!Edit existing line/boundary
  492. @param line id of line to be modified
  493. @param coords list of coordinates of modified line
  494. @return feature id of new line
  495. @return -1 on error
  496. """
  497. if self._checkMap():
  498. return -1
  499. try:
  500. lineid = line[0]
  501. except:
  502. lineid = -1
  503. if len(coords) < 2:
  504. self.DeleteSelectedLines()
  505. return 0
  506. ret = self.digit.RewriteLine(lineid, listCoords,
  507. bgmap, self._getSnapMode(),
  508. self._display.GetThreshold())
  509. if ret > 0:
  510. self.toolbar.EnableUndo()
  511. return ret
  512. def FlipLine(self):
  513. """!Flip selected lines/boundaries
  514. @return number of modified lines
  515. @return -1 on error
  516. """
  517. if not self._checkMap():
  518. return -1
  519. nlines = Vect_get_num_lines(self.poMapInfo)
  520. # register changeset
  521. changeset = self._addActionsBefore()
  522. poList = self._display.GetSelectedIList()
  523. ret = Vedit_flip_lines(self.poMapInfo, poList)
  524. Vect_destroy_list(poList)
  525. if ret > 0:
  526. self._addActionsAfter(changeset, nlines)
  527. self.toolbar.EnableUndo()
  528. else:
  529. del self.changesets[changeset]
  530. return ret
  531. def MergeLine(self):
  532. """!Merge selected lines/boundaries
  533. @return number of modified lines
  534. @return -1 on error
  535. """
  536. if not self._checkMap():
  537. return -1
  538. nlines = Vect_get_num_lines(self.poMapInfo)
  539. changeset = self._addActionsBefore()
  540. poList = self._display.GetSelectedIList()
  541. ret = Vedit_merge_lines(self.poMapInfo, poList)
  542. Vect_destroy_list(poList)
  543. if ret > 0:
  544. self._addActionsAfter(changeset, nlines)
  545. self.toolbar.EnableUndo()
  546. else:
  547. del self.changesets[changeset]
  548. return ret
  549. def BreakLine(self):
  550. """!Break selected lines/boundaries
  551. @return number of modified lines
  552. @return -1 on error
  553. """
  554. if not self._checkMap():
  555. return -1
  556. nlines = Vect_get_num_lines(self.poMapInfo)
  557. changeset = self._addActionsBefore()
  558. poList = self._display.GetSelectedIList()
  559. ret = Vect_break_lines_list(self.poMapInfo, poList, None,
  560. GV_LINES, None)
  561. Vect_destroy_list(poList)
  562. if ret > 0:
  563. self._addActionsAfter(changeset, nlines)
  564. self.toolbar.EnableUndo()
  565. else:
  566. del self.changesets[changeset]
  567. return ret
  568. def SnapLine(self):
  569. """!Snap selected lines/boundaries
  570. @return on success
  571. @return -1 on error
  572. """
  573. if not self._checkMap():
  574. return -1
  575. nlines = Vect_get_num_lines(self.poMapInfo)
  576. changeset = self._addActionsBefore()
  577. poList = self._display.GetSelectedIList()
  578. Vect_snap_lines_list(self.poMapInfo, poList,
  579. self._display.GetThreshold(), None)
  580. Vect_destroy_list(poList)
  581. if nlines < Vect_get_num_lines(self.poMapInfo):
  582. self._addActionsAfter(changeset, nlines)
  583. self.toolbar.EnableUndo()
  584. else:
  585. del self.changesets[changeset]
  586. def ConnectLine(self):
  587. """!Connect selected lines/boundaries
  588. @return 1 lines connected
  589. @return 0 lines not connected
  590. @return -1 on error
  591. """
  592. if not self._checkMap():
  593. return -1
  594. nlines = Vect_get_num_lines(self.poMapInfo)
  595. # register changeset
  596. changeset = self._addActionsBefore()
  597. poList = self._display.GetSelectedIList()
  598. ret = Vedit_connect_lines(self.poMapInfo, poList,
  599. self._display.GetThreshold())
  600. Vect_destroy_list(poList)
  601. if ret > 0:
  602. self._addActionsAfter(changeset, nlines)
  603. self.toolbar.EnableUndo()
  604. else:
  605. del self.changesets[changeset]
  606. return ret
  607. def CopyLine(self, ids = []):
  608. """!Copy features from (background) vector map
  609. @param ids list of line ids to be copied
  610. @return number of copied features
  611. @return -1 on error
  612. """
  613. if not self._checkMap():
  614. return -1
  615. nlines = Vect_get_num_lines(self.poMapInfo)
  616. poList = self._display.GetSelectedIList(ids)
  617. ret = Vedit_copy_lines(self.poMapInfo, self.poBgMapInfo,
  618. poList)
  619. Vect_destroy_list(poList)
  620. if ret > 0:
  621. changeset = len(self.changesets)
  622. for line in (range(nlines + 1, Vect_get_num_lines(self.poMapInfo))):
  623. self._addActionToChangeset(changeset, line, add = True)
  624. self.toolbar.EnableUndo()
  625. else:
  626. del self.changesets[changeset]
  627. if ret > 0 and self.poBgMapInfo and self.settings['breakLines']:
  628. for i in range(1, ret):
  629. self._breakLineAtIntersection(nlines + i, None, changeset)
  630. return ret
  631. def CopyCats(self, fromId, toId, copyAttrb=False):
  632. """!Copy given categories to objects with id listed in ids
  633. @param cats ids of 'from' feature
  634. @param ids ids of 'to' feature(s)
  635. @return number of modified features
  636. @return -1 on error
  637. """
  638. if len(fromId) == 0 or len(toId) == 0:
  639. return 0
  640. ret = self.digit.CopyCats(fromId, toId, copyAttrb)
  641. if ret > 0:
  642. self.toolbar.EnableUndo()
  643. return ret
  644. def _selectLinesByQueryThresh(self):
  645. """!Generic method used for SelectLinesByQuery() -- to get
  646. threshold value"""
  647. thresh = 0.0
  648. if UserSettings.Get(group = 'vdigit', key = 'query', subkey = 'selection') == 0:
  649. thresh = UserSettings.Get(group = 'vdigit', key = 'queryLength', subkey = 'thresh')
  650. if UserSettings.Get(group = 'vdigit', key = "queryLength", subkey = 'than-selection') == 0:
  651. thresh = -1 * thresh
  652. else:
  653. thresh = UserSettings.Get(group = 'vdigit', key = 'queryDangle', subkey = 'thresh')
  654. if UserSettings.Get(group = 'vdigit', key = "queryDangle", subkey = 'than-selection') == 0:
  655. thresh = -1 * thresh
  656. return thresh
  657. def SelectLinesByQuery(self, bbox):
  658. """!Select features by query
  659. @todo layer / 3D
  660. @param bbox bounding box definition
  661. """
  662. if not self._checkMap():
  663. return -1
  664. thresh = self._selectLinesByQueryThresh()
  665. query = QUERY_UNKNOWN
  666. if UserSettings.Get(group = 'vdigit', key = 'query', subkey = 'selection') == 0:
  667. query = QUERY_LENGTH
  668. else:
  669. query = QUERY_DANGLE
  670. ftype = GV_POINTS | GV_LINES # TODO: 3D
  671. layer = 1 # TODO
  672. ids = list()
  673. poList = Vect_new_list()
  674. coList = poList.contents
  675. if UserSettings.Get(group = 'vdigit', key = 'query', subkey = 'box'):
  676. Vect_reset_line(self.poPoints)
  677. x1, y1 = bbox[0]
  678. x2, y2 = bbox[1]
  679. z1 = z2 = 0.0
  680. Vect_append_point(self.poPoints, x1, y1, z1)
  681. Vect_append_point(self.poPoints, x2, y1, z2)
  682. Vect_append_point(self.poPoints, x2, y2, z1)
  683. Vect_append_point(self.poPoints, x1, y2, z2)
  684. Vect_append_point(self.poPoints, x1, y1, z1)
  685. Vect_select_lines_by_polygon(self.poMapInfo, self.poPoints, 0, None,
  686. ftype, poList)
  687. if coList.n_values == 0:
  688. return ids
  689. Vedit_select_by_query(self.poMapInfo,
  690. ftype, layer, thresh, query,
  691. poList)
  692. for i in range(coList.n_values):
  693. ids.append(int(coList.value[i]))
  694. Debug.msg(3, "IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values)
  695. Vect_destroy_list(poList)
  696. return ids
  697. def IsVector3D(self):
  698. """!Check if open vector map is 3D
  699. """
  700. if not self._checkMap():
  701. return False
  702. return Vect_is_3d(self.poMapInfo)
  703. def GetLineCats(self, line=-1):
  704. """!Get layer/category pairs from given (selected) line
  705. @param line feature id (-1 for first selected line)
  706. """
  707. return dict(self.digit.GetLineCats(line))
  708. def GetLineLength(self, line):
  709. """!Get line length
  710. @param line feature id
  711. @return line length
  712. @return -1 on error
  713. """
  714. return self.digit.GetLineLength(line)
  715. def GetAreaSize(self, centroid):
  716. """!Get area size
  717. @param centroid centroid id
  718. @return area size
  719. @return -1 on error
  720. """
  721. return self.digit.GetAreaSize(centroid)
  722. def GetAreaPerimeter(self, centroid):
  723. """!Get area perimeter
  724. @param centroid centroid id
  725. @return area size
  726. @return -1 on error
  727. """
  728. return self.digit.GetAreaPerimeter(centroid)
  729. def SetLineCats(self, line, layer, cats, add=True):
  730. """!Set categories for given line and layer
  731. @param line feature id
  732. @param layer layer number (-1 for first selected line)
  733. @param cats list of categories
  734. @param add if True to add, otherwise do delete categories
  735. @return new feature id (feature need to be rewritten)
  736. @return -1 on error
  737. """
  738. ret = self.digit.SetLineCats(line, layer, cats, add)
  739. if ret > 0:
  740. self.toolbar.EnableUndo()
  741. return ret
  742. def GetLayers(self):
  743. """!Get list of layers"""
  744. return self.digit.GetLayers()
  745. def TypeConvForSelectedLines(self):
  746. """!Feature type conversion for selected objects.
  747. Supported conversions:
  748. - point <-> centroid
  749. - line <-> boundary
  750. @return number of modified features
  751. @return -1 on error
  752. """
  753. if not self._checkMap():
  754. return -1
  755. nlines = Vect_get_num_lines(self.poMapInfo)
  756. # register changeset
  757. changeset = self._addActionsBefore()
  758. poList = self._display.GetSelectedIList()
  759. ret = Vedit_chtype_lines(self.poMapInfo, poList)
  760. Vect_destroy_list(poList)
  761. if ret > 0:
  762. self._addActionsAfter(changeset, nlines)
  763. self.toolbar.EnableUndo()
  764. else:
  765. del self.changesets[changeset]
  766. return ret
  767. def Undo(self, level = -1):
  768. """!Undo action
  769. @param level levels to undo (0 to revert all)
  770. @return id of current changeset
  771. """
  772. changesetLast = len(self.changesets.keys()) - 1
  773. if changesetLast < 0:
  774. return changesetLast
  775. if self.changesetCurrent == -2: # value uninitialized
  776. self.changesetCurrent = changesetLast
  777. if level > 0 and self.changesetCurrent < 0:
  778. self.changesetCurrent = 0
  779. if level == 0:
  780. # 0 -> undo all
  781. level = -1 * changesetLast + 1
  782. Debug.msg(2, "Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d",
  783. changesetLast, self.changesetCurrent, level)
  784. if level < 0: # undo
  785. if self.changesetCurrent + level < -1:
  786. return changesetCurrent;
  787. for changeset in range(self.changesetCurrent, self.changesetCurrent + level, -1):
  788. self._applyChangeset(changeset, undo = True)
  789. elif level > 0: # redo
  790. if self.changesetCurrent + level > len(self.changesets.keys()):
  791. return self.changesetCurrent
  792. for changeset in range(self.changesetCurrent, self.changesetCurrent + level):
  793. self._applyChangeset(changeset, undo = False)
  794. self.changesetCurrent += level
  795. Debug.msg(2, "Digit.Undo(): changeset_current=%d, changeset_last=%d, changeset_end=%d",
  796. self.changesetCurrent, changesetLast, self.changesetEnd)
  797. if self.changesetCurrent == self.changesetEnd:
  798. self.changesetEnd = changesetLast
  799. return -1
  800. self.mapWindow.UpdateMap(render = False)
  801. if self.changesetCurrent < 0: # disable undo tool
  802. self.toolbar.EnableUndo(False)
  803. def ZBulkLines(self, pos1, pos2, start, step):
  804. """!Z-bulk labeling
  805. @param pos1 reference line (start point)
  806. @param pos1 reference line (end point)
  807. @param start starting value
  808. @param step step value
  809. @return number of modified lines
  810. @return -1 on error
  811. """
  812. if not self._checkMap():
  813. return -1
  814. nlines = Vect_get_num_lines(self.poMapInfo)
  815. # register changeset
  816. changeset = self._addActionsBefore()
  817. poList = self._display.GetSelectedIList()
  818. ret = Vedit_bulk_labeling(self.poMapInfo, poList,
  819. pos1[0], pos1[1], pos2[0], pos2[1],
  820. start, step)
  821. Vect_destroy_list(poList)
  822. if ret > 0:
  823. self._addActionsAfter(changeset, nlines)
  824. self.toolbar.EnableUndo()
  825. else:
  826. del self.changesets[changeset]
  827. return ret
  828. def GetDisplay(self):
  829. """!Get display driver instance"""
  830. return self._display
  831. def OpenMap(self, name):
  832. """!Open vector map for editing
  833. @param map name of vector map to be set up
  834. """
  835. Debug.msg (3, "AbstractDigit.SetMapName map=%s" % name)
  836. name, mapset = name.split('@')
  837. self.poMapInfo = self._display.OpenMap(str(name), str(mapset), True)
  838. if self.poMapInfo:
  839. self.InitCats()
  840. return self.poMapInfo
  841. def CloseMap(self):
  842. """!Close currently open vector map
  843. """
  844. if not self._checkMap():
  845. return
  846. self._display.CloseMap()
  847. def InitCats(self):
  848. """!Initialize categories information
  849. @return 0 on success
  850. @return -1 on error
  851. """
  852. self.cats.clear()
  853. if not self._checkMap():
  854. return -1
  855. ndblinks = Vect_get_num_dblinks(self.poMapInfo)
  856. for i in range(ndblinks):
  857. fi = Vect_get_dblink(self.poMapInfo, i).contents
  858. if fi:
  859. self.cats[fi.number] = None
  860. # find max category
  861. nfields = Vect_cidx_get_num_fields(self.poMapInfo)
  862. Debug.msg(2, "wxDigit.InitCats(): nfields=%d", nfields)
  863. for i in range(nfields):
  864. field = Vect_cidx_get_field_number(self.poMapInfo, i)
  865. ncats = Vect_cidx_get_num_cats_by_index(self.poMapInfo, i)
  866. if field <= 0:
  867. continue
  868. for j in range(ncats):
  869. cat = c_int()
  870. type = c_int()
  871. id = c_int()
  872. Vect_cidx_get_cat_by_index(self.poMapInfo, i, j,
  873. byref(cat), byref(type), byref(id))
  874. if self.cats.has_key(field):
  875. if cat > self.cats[field]:
  876. self.cats[field] = cat.value
  877. else:
  878. self.cats[field] = cat.value
  879. Debug.msg(3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field])
  880. # set default values
  881. for field, cat in self.cats.iteritems():
  882. if cat == None:
  883. self.cats[field] = 0 # first category 1
  884. Debug.msg(3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field])
  885. def _checkMap(self):
  886. """!Check if map is open
  887. """
  888. if not self.poMapInfo:
  889. self._error.NoMap()
  890. return False
  891. return True
  892. def _addFeature(self, type, coords, layer, cat, snap, threshold):
  893. """!Add new feature to the vector map
  894. @param type feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...)
  895. @coords tuple of coordinates ((x, y), (x, y), ...)
  896. @param layer layer number (-1 for no cat)
  897. @param cat category number
  898. @param snap snap to node/vertex
  899. @param threshold threshold for snapping
  900. @return -1 on error
  901. @return feature id of new feature
  902. """
  903. if not self._checkMap():
  904. return -1
  905. is3D = bool(Vect_is_3d(self.poMapInfo))
  906. Debug.msg(2, "IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d",
  907. len(coords), layer, cat, snap)
  908. if not (type & (GV_POINTS | GV_LINES)): # TODO: 3D
  909. return -1
  910. # set category
  911. Vect_reset_cats(self.poCats)
  912. if layer > 0 and \
  913. (type != GV_BOUNDARY or \
  914. (type == GV_BOUNDARY and self.settings['catBoundary'])):
  915. Vect_cat_set(self.poCats, layer, cat)
  916. self.cats[layer] = max(cat, self.cats.get(layer, 0))
  917. # append points
  918. Vect_reset_line(self.poPoints)
  919. for c in coords:
  920. Vect_append_point(self.poPoints, c[0], c[1], 0.0)
  921. if type & GV_BOUNDARY:
  922. # close boundary
  923. points = self.poPoints.contents
  924. last = points.n_points - 1
  925. if Vect_points_distance(points.x[0], points.x[0], points.z[0],
  926. points.x[last], points.x[last], points.z[last],
  927. is3D) <= threshold:
  928. points.x[last] = points.x[0]
  929. points.y[last] = points.y[0]
  930. points.z[last] = points.z[0]
  931. if snap != NO_SNAP and (type & (GV_POINT | GV_LINES)):
  932. # apply snapping (node or vertex)
  933. modeSnap = not (snap == SNAP)
  934. Vedit_snap_line(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
  935. -1, self.poPoints, threshold, modeSnap)
  936. newline = Vect_write_line(self.poMapInfo, type, self.poPoints, self.poCats)
  937. if newline < 0:
  938. self._error.WriteLine()
  939. return -1
  940. left = right = -1
  941. if type & GV_BOUNDARY and self.settings['addCentroid']:
  942. # add centroids for left/right area
  943. bpoints = Vect_new_line_struct()
  944. cleft = c_int()
  945. cright = c_int()
  946. Vect_get_line_areas(self.poMapInfo, newline,
  947. byref(cleft), byref(cright))
  948. left = cleft.value
  949. right = cright.value
  950. # check if area exists and has no centroid inside
  951. if layer > 0 and (left > 0 or right > 0):
  952. Vect_cat_set(self.poCats, layer, cat)
  953. self.cats[layer] = max(cat, self.cats.get(layer, 0))
  954. x = c_double()
  955. y = c_double()
  956. if left > 0 and \
  957. Vect_get_area_centroid(self.poMapInfo, left) == 0:
  958. if Vect_get_area_points(self.poMapInfo, left, bpoints) > 0 and \
  959. Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
  960. Vect_reset_line(bpoints)
  961. Vect_append_point(bpoints, x.value, y.value, 0.0)
  962. if Vect_write_line(self.poMapInfo, GV_CENTROID,
  963. bpoints, self.poCats) < 0:
  964. self._error.WriteLine()
  965. return -1
  966. if right > 0 and \
  967. Vect_get_area_centroid(self.poMapInfo, right) == 0:
  968. if Vect_get_area_points(byref(self.poMapInfo), right, bpoints) > 0 and \
  969. Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
  970. Vect_reset_line(bpoints)
  971. Vect_append_point(bpoints, x.value, y.value, 0.0)
  972. if Vect_write_line(byref(self.poMapInfo), GV_CENTROID,
  973. bpoints, self.poCats) < 0:
  974. self._error.WriteLine()
  975. return -1
  976. Vect_destroy_line_struct(bpoints)
  977. # register changeset
  978. self._addActionToChangeset(len(self.changesets), newline, add = True)
  979. # break at intersection
  980. if self.settings['breakLines']:
  981. self._breakLineAtIntersection(newline, self.poPoints, changeset)
  982. if type & GV_BOUNDARY and \
  983. not self.settings['catBoundary'] and \
  984. left < 1 and right < 1:
  985. newline = None # ?
  986. return newline
  987. def _ModifyLineVertex(self, coords, add = True):
  988. """!Add or remove vertex
  989. Shape of line/boundary is not changed when adding new vertex.
  990. @param coords coordinates of point
  991. @param add True to add, False to remove
  992. @return id id of the new feature
  993. @return 0 nothing changed
  994. @return -1 error
  995. """
  996. if not self._checkMap():
  997. return -1
  998. selected = self._display.selected
  999. if len(selected['ids']) != 1:
  1000. return 0
  1001. poList = self._display.GetSelectedIList()
  1002. Vect_reset_line(self.poPoints)
  1003. Vect_append_point(self.poPoints, coords[0], coords[1], 0.0)
  1004. nlines = Vect_get_num_lines(self.poMapInfo)
  1005. thresh = self._display.GetThreshold(type = 'selectThresh')
  1006. changeset = self._addActionsBefore()
  1007. if add:
  1008. ret = Vedit_add_vertex(self.poMapInfo, poList,
  1009. self.poPoints, thresh)
  1010. else:
  1011. ret = Vedit_remove_vertex(self.poMapInfo, poList,
  1012. self.poPoints, thresh)
  1013. Vect_destroy_list(poList)
  1014. if ret > 0:
  1015. self._addActionsAfter(changeset, nlines)
  1016. else:
  1017. del self.changesets[changeset]
  1018. if not add and ret > 0 and self.settings['breakLines']:
  1019. self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
  1020. None, changeset)
  1021. return nlines + 1 # feature is write at the end of the file
  1022. def GetLineCats(self, line):
  1023. """!Get list of layer/category(ies) for selected feature.
  1024. @param line feature id (-1 for first selected feature)
  1025. @return list of layer/cats
  1026. """
  1027. ret = dict()
  1028. if not self._checkMap():
  1029. return ret
  1030. if line == -1 and len(self._display.selected['ids']) < 1:
  1031. return ret
  1032. if line == -1:
  1033. line = self._display.selected['ids'][0]
  1034. if not Vect_line_alive(self.poMapInfo, line):
  1035. self._error.DeadLine(line)
  1036. return ret
  1037. if Vect_read_line(self.poMapInfo, None, self.poCats, line) < 0:
  1038. self._error.ReadLine(line)
  1039. return ret
  1040. cats = self.poCats.contents
  1041. for i in range(cats.n_cats):
  1042. field = cats.field[i]
  1043. if field not in ret:
  1044. ret[field] = list()
  1045. ret[field].append(cats.cat[i])
  1046. return ret
  1047. def GetLayers(self):
  1048. """!Get list of layers
  1049. Requires self.InitCats() to be called.
  1050. @return list of layers
  1051. """
  1052. return self.cats.keys()
  1053. def UpdateSettings(self, breakLines, addCentroid, catBoundary):
  1054. """!Update digit settings
  1055. @param breakLines break lines on intersection
  1056. @param addCentroid add centroid to left/right area
  1057. @param catBoundary attach category to boundary
  1058. """
  1059. self._settings['breakLines'] = breakLines
  1060. self._settings['addCentroid'] = addCentroid
  1061. self._settings['catBoundary'] = None # !catBoundary # do not attach
  1062. def _getCategory(self):
  1063. """!Get current category number to be use"""
  1064. if not UserSettings.Get(group = 'vdigit', key = 'categoryMode', subkey = 'selection'):
  1065. self.SetCategoryNextToUse()
  1066. return UserSettings.Get(group = 'vdigit', key = 'category', subkey = 'value')
  1067. def SetCategoryNextToUse(self):
  1068. """!Find maximum category number for the given layer and
  1069. update the settings
  1070. """
  1071. # reset 'category' to '1' (for maps with no attributes)
  1072. UserSettings.Set(group = 'vdigit', key = 'category', subkey = 'value', value = 1)
  1073. # get max category number for given layer and update the settings
  1074. cat = self.cats.get(UserSettings.Get(group = 'vdigit', key = 'layer', subkey = 'value'), 0)
  1075. cat += 1
  1076. UserSettings.Set(group = 'vdigit', key = 'category', subkey = 'value',
  1077. value = cat)
  1078. def SelectLinesFromBackgroundMap(self, bbox):
  1079. """!Select features from background map
  1080. @param bbox bounding box definition
  1081. @return list of selected feature ids
  1082. """
  1083. ret = list()
  1084. # try select features by Box
  1085. ids = self._display.SelectLinesByBox(bbox, poMapInfo = self.poBgMapInfo)
  1086. if not ids:
  1087. ids = [self._display.SelectLineByPoint(bbox[0], poMapInfo = self.poBgMapInfo)['line'], ]
  1088. return ids
  1089. def GetUndoLevel(self):
  1090. """!Get undo level (number of active changesets)
  1091. Note: Changesets starts wiht 0
  1092. """
  1093. return self.changesetCurrent