wxvdigit.py 52 KB

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