wxvdigit.py 39 KB

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