wxvdigit.py 47 KB

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