wxdigit.py 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147
  1. """
  2. @package vdigit.wxdigit
  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. - wxdigit::VDigitError
  8. - wxdigit::IVDigit
  9. .. todo::
  10. Read large amounts of data from Vlib into arrays, which could
  11. then be processed using NumPy and rendered using glDrawArrays or
  12. glDrawElements, so no per-line/per-vertex processing in Python. Bulk
  13. data processing with NumPy is much faster than iterating in Python
  14. (and NumPy would be an excellent candidate for acceleration via
  15. e.g. OpenCL or CUDA; I'm surprised it hasn't happened already).
  16. (C) 2007-2016 by the GRASS Development Team
  17. This program is free software under the GNU General Public License
  18. (>=v2). Read the file COPYING that comes with GRASS for details.
  19. @author Martin Landa <landa.martin gmail.com>
  20. """
  21. from __future__ import print_function
  22. import grass.script.core as grass
  23. from grass.pydispatch.signal import Signal
  24. from core.gcmd import GError
  25. from core.debug import Debug
  26. from core.settings import UserSettings
  27. from core.utils import _
  28. from vdigit.wxdisplay import DisplayDriver, GetLastError
  29. try:
  30. from grass.lib.gis import *
  31. from grass.lib.vector import *
  32. from grass.lib.vedit import *
  33. from grass.lib.dbmi import *
  34. except (ImportError, WindowsError) as e:
  35. print("wxdigit.py: {}".format(e), file=sys.stderr)
  36. class VDigitError:
  37. def __init__(self, parent):
  38. """Class for managing error messages of vector digitizer
  39. :param parent: parent window for dialogs
  40. """
  41. self.parent = parent
  42. self.caption = _('Digitization Error')
  43. def NoMap(self, name=None):
  44. """No map for editing"""
  45. if name:
  46. message = _('Unable to open vector map <%s>.') % name
  47. else:
  48. message = _('No vector map open for editing.')
  49. GError(message + ' ' + _('Operation canceled.'),
  50. parent=self.parent,
  51. caption=self.caption)
  52. def WriteLine(self):
  53. """Writing line failed
  54. """
  55. GError(message=_('Writing new feature failed. '
  56. 'Operation canceled.\n\n'
  57. 'Reason: %s') % GetLastError(),
  58. parent=self.parent,
  59. caption=self.caption)
  60. def ReadLine(self, line):
  61. """Reading line failed
  62. """
  63. GError(message=_('Reading feature id %d failed. '
  64. 'Operation canceled.') % line,
  65. parent=self.parent,
  66. caption=self.caption)
  67. def DbLink(self, dblink):
  68. """No dblink available
  69. """
  70. GError(message=_('Database link %d not available. '
  71. 'Operation canceled.') % dblink,
  72. parent=self.parent,
  73. caption=self.caption)
  74. def Driver(self, driver):
  75. """Staring driver failed
  76. """
  77. GError(message=_('Unable to start database driver <%s>. '
  78. 'Operation canceled.') % driver,
  79. parent=self.parent,
  80. caption=self.caption)
  81. def Database(self, driver, database):
  82. """Opening database failed
  83. """
  84. GError(
  85. message=_(
  86. 'Unable to open database <%(db)s> by driver <%(driver)s>. '
  87. 'Operation canceled.') %
  88. {'db': database, 'driver': driver},
  89. parent=self.parent, caption=self.caption)
  90. def DbExecute(self, sql):
  91. """Sql query failed
  92. """
  93. GError(message=_("Unable to execute SQL query '%s'. "
  94. "Operation canceled.") % sql,
  95. parent=self.parent,
  96. caption=self.caption)
  97. def DeadLine(self, line):
  98. """Dead line
  99. """
  100. GError(message=_("Feature id %d is marked as dead. "
  101. "Operation canceled.") % line,
  102. parent=self.parent,
  103. caption=self.caption)
  104. def FeatureType(self, ftype):
  105. """Unknown feature type
  106. """
  107. GError(message=_("Unsupported feature type %d. "
  108. "Operation canceled.") % ftype,
  109. parent=self.parent,
  110. caption=self.caption)
  111. class IVDigit:
  112. def __init__(self, mapwindow, driver=DisplayDriver):
  113. """Base class for vector digitizer (ctypes interface)
  114. :param mapwindow: reference to a map window
  115. """
  116. self.poMapInfo = None # pointer to Map_info
  117. self.mapWindow = mapwindow
  118. # background map
  119. self.bgMapInfo = Map_info()
  120. self.poBgMapInfo = self.popoBgMapInfo = None
  121. # TODO: replace this by using giface
  122. if not mapwindow.parent.IsStandalone():
  123. goutput = mapwindow.parent.GetLayerManager().GetLogWindow()
  124. log = goutput.GetLog(err=True)
  125. progress = mapwindow.parent._giface.GetProgress()
  126. else:
  127. log = sys.stderr
  128. progress = None
  129. self.toolbar = mapwindow.parent.toolbars['vdigit']
  130. self._error = VDigitError(parent=self.mapWindow)
  131. self._display = driver(device=mapwindow.pdcVector,
  132. deviceTmp=mapwindow.pdcTmp,
  133. mapObj=mapwindow.Map,
  134. window=mapwindow,
  135. glog=log,
  136. gprogress=progress)
  137. # GRASS lib
  138. self.poPoints = Vect_new_line_struct()
  139. self.poCats = Vect_new_cats_struct()
  140. # self.SetCategory()
  141. # layer / max category
  142. self.cats = dict()
  143. self._settings = dict()
  144. self.UpdateSettings() # -> self._settings
  145. # undo/redo
  146. self.changesets = list()
  147. self.changesetCurrent = -1 # first changeset to apply
  148. if self.poMapInfo:
  149. self.InitCats()
  150. self.emit_signals = False
  151. # signals which describes features changes during digitization,
  152. # activate them using EmitSignals method
  153. # currently implemented for functionality used by wx.iclass (for
  154. # scatter plot)
  155. # signals parameter description:
  156. # old_bboxs - list of bboxes of boundary features, which covers changed areas
  157. # it is bbox of old state (before edit)
  158. # old_areas_cats - list of area categories of boundary features of old state (before edit)
  159. # same position in both lists corresponds to same feature
  160. # new_bboxs = list of bboxes of created features / after edit
  161. # new_areas_cats list of areas cats of created features / after edit
  162. # same position in both lists corresponds to same features
  163. # for description of items in bbox and area_cats lists see return value
  164. # of _getaAreaBboxCats
  165. # TODO currently it is not possible to identify corresponded features
  166. # in old and new lists (requires changed to vector updated format)
  167. # TODO return feature type
  168. # TODO handle errors?
  169. self.featureAdded = Signal('IVDigit.featureAdded')
  170. self.areasDeleted = Signal('IVDigit.areasDeleted')
  171. self.vertexMoved = Signal('IVDigit.vertexMoved')
  172. self.vertexAdded = Signal('IVDigit.vertexAdded')
  173. self.vertexRemoved = Signal('IVDigit.vertexRemoved')
  174. self.featuresDeleted = Signal('IVDigit.featuresDeleted')
  175. self.featuresMoved = Signal('IVDigit.featuresMoved')
  176. self.lineEdited = Signal('IVDigit.lineEdited')
  177. def __del__(self):
  178. Debug.msg(1, "IVDigit.__del__()")
  179. Vect_destroy_line_struct(self.poPoints)
  180. self.poPoints = None
  181. Vect_destroy_cats_struct(self.poCats)
  182. self.poCats = None
  183. if self.poBgMapInfo:
  184. Vect_close(self.poBgMapInfo)
  185. self.poBgMapInfo = self.popoBgMapInfo = None
  186. del self.bgMapInfo
  187. def EmitSignals(self, emit):
  188. """Activate/deactivate signals which describes features changes during digitization.
  189. """
  190. self.emit_signals = emit
  191. def CloseBackgroundMap(self):
  192. """Close background vector map"""
  193. if not self.poBgMapInfo:
  194. return
  195. Vect_close(self.poBgMapInfo)
  196. self.poBgMapInfo = self.popoBgMapInfo = None
  197. def OpenBackgroundMap(self, bgmap):
  198. """Open background vector map
  199. .. todo::
  200. support more background maps then only one
  201. :param bgmap: name of vector map to be opened
  202. :type bgmap: str
  203. :return: pointer to map_info
  204. :return: None on error
  205. """
  206. name = create_string_buffer(GNAME_MAX)
  207. mapset = create_string_buffer(GMAPSET_MAX)
  208. if not G_name_is_fully_qualified(bgmap, name, mapset):
  209. name = str(bgmap)
  210. mapset = str(G_find_vector2(bgmap, ''))
  211. else:
  212. name = str(name.value)
  213. mapset = str(mapset.value)
  214. if (name == Vect_get_name(self.poMapInfo) and
  215. mapset == Vect_get_mapset(self.poMapInfo)):
  216. self.poBgMapInfo = self.popoBgMapInfo = None
  217. self._error.NoMap(bgmap)
  218. return
  219. self.poBgMapInfo = pointer(self.bgMapInfo)
  220. self.popoBgMapInfo = pointer(self.poBgMapInfo)
  221. if Vect_open_old(self.poBgMapInfo, name, mapset) == -1:
  222. self.poBgMapInfo = self.popoBgMapInfo = None
  223. self._error.NoMap(bgmap)
  224. return
  225. def _getSnapMode(self):
  226. """Get snapping mode
  227. - snap to vertex
  228. - snap to nodes
  229. - no snapping
  230. :return: snap mode
  231. """
  232. threshold = self._display.GetThreshold()
  233. if threshold > 0.0:
  234. if UserSettings.Get(
  235. group='vdigit', key='snapToVertex', subkey='enabled'):
  236. return SNAPVERTEX
  237. else:
  238. return SNAP
  239. else:
  240. return NO_SNAP
  241. def _getNewFeaturesLayer(self):
  242. """Returns layer of new feature (from settings)"""
  243. if UserSettings.Get(group='vdigit', key="categoryMode",
  244. subkey='selection') == 2:
  245. layer = -1 # -> no category
  246. else:
  247. layer = UserSettings.Get(
  248. group='vdigit', key="layer", subkey='value')
  249. return layer
  250. def _getNewFeaturesCat(self):
  251. """Returns category of new feature (from settings)"""
  252. if UserSettings.Get(group='vdigit', key="categoryMode",
  253. subkey='selection') == 2:
  254. cat = -1
  255. else:
  256. cat = self.SetCategory()
  257. return cat
  258. def _breakLineAtIntersection(self, line, pointsLine):
  259. """Break given line at intersection
  260. :param line: line id
  261. :param pointsLine: line geometry
  262. :return: number of modified lines
  263. """
  264. if not self._checkMap():
  265. return -1
  266. if not Vect_line_alive(self.poMapInfo, line):
  267. return 0
  268. if not pointsLine:
  269. if Vect_read_line(self.poMapInfo, self.poPoints, None, line) < 0:
  270. self._error.ReadLine(line)
  271. return -1
  272. points = self.poPoints
  273. else:
  274. points = pointsLine
  275. listLine = Vect_new_boxlist(0)
  276. listRef = Vect_new_list()
  277. listBreak = Vect_new_list()
  278. pointsCheck = Vect_new_line_struct()
  279. lineBox = bound_box()
  280. # find all relevant lines
  281. Vect_get_line_box(self.poMapInfo, line, byref(lineBox))
  282. Vect_select_lines_by_box(self.poMapInfo, byref(lineBox),
  283. GV_LINES, listLine)
  284. # check for intersection
  285. Vect_list_append(listBreak, line)
  286. Vect_list_append(listRef, line)
  287. for i in range(listLine.contents.n_values):
  288. lineBreak = listLine.contents.id[i]
  289. if lineBreak == line:
  290. continue
  291. ltype = Vect_read_line(
  292. self.poMapInfo, pointsCheck, None, lineBreak)
  293. if not (ltype & GV_LINES):
  294. continue
  295. if Vect_line_check_intersection(self.poPoints, pointsCheck,
  296. WITHOUT_Z):
  297. Vect_list_append(listBreak, lineBreak)
  298. ret = Vect_break_lines_list(self.poMapInfo, listBreak, listRef,
  299. GV_LINES, None)
  300. Vect_destroy_line_struct(pointsCheck)
  301. Vect_destroy_boxlist(listLine)
  302. Vect_destroy_list(listBreak)
  303. Vect_destroy_list(listRef)
  304. return ret
  305. def _addChangeset(self):
  306. # disable redo
  307. changesetLast = len(self.changesets) - 1
  308. if self.changesetCurrent < changesetLast and len(self.changesets) > 0:
  309. del self.changesets[self.changesetCurrent + 1:changesetLast + 1]
  310. self.toolbar.EnableRedo(False)
  311. data = list()
  312. for i in range(Vect_get_num_updated_lines(self.poMapInfo) - 1, -1, -1):
  313. line = Vect_get_updated_line(self.poMapInfo, i)
  314. offset = Vect_get_updated_line_offset(self.poMapInfo, i)
  315. data.append({'line': line,
  316. 'offset': offset})
  317. self.changesetCurrent += 1
  318. self.changesets.insert(self.changesetCurrent, data)
  319. Vect_reset_updated(self.poMapInfo)
  320. def _applyChangeset(self, changeset, undo):
  321. """Apply changeset (undo/redo changeset)
  322. :param changeset: changeset id
  323. :param undo: True for undo otherwise redo
  324. :type undo: bool
  325. :return: 1 changeset applied
  326. :return: 0 changeset not applied
  327. :return: -1 on error
  328. """
  329. if changeset < 0 or changeset >= len(self.changesets):
  330. return -1
  331. ret = 0
  332. actions = self.changesets[changeset]
  333. if undo:
  334. firstaction = 0
  335. lastaction = len(actions)
  336. step = 1
  337. else:
  338. firstaction = len(actions) - 1
  339. lastaction = -1
  340. step = -1
  341. for i in range(firstaction, lastaction, step):
  342. action = actions[i]
  343. line = action['line']
  344. if action['offset'] > 0:
  345. # feature previously added -> to be deleted
  346. if Vect_line_alive(self.poMapInfo, line):
  347. Debug.msg(
  348. 3,
  349. "IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted",
  350. changeset,
  351. line)
  352. Vect_delete_line(self.poMapInfo, line)
  353. ret = 1
  354. else:
  355. Debug.msg(
  356. 3,
  357. "Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead",
  358. changeset,
  359. line)
  360. else:
  361. # feature previously deleted -> to be added
  362. offset = abs(action['offset'])
  363. if not Vect_line_alive(self.poMapInfo, line):
  364. Debug.msg(
  365. 3,
  366. "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added",
  367. changeset,
  368. line)
  369. if Vect_restore_line(self.poMapInfo, offset, line) < 0:
  370. return -1
  371. ret = 1
  372. else:
  373. Debug.msg(
  374. 3,
  375. "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive",
  376. changeset,
  377. line)
  378. action['offset'] *= -1
  379. Vect_reset_updated(self.poMapInfo)
  380. return ret
  381. def AddFeature(self, ftype, points):
  382. """Add new feature
  383. :param ftype: feature type (point, line, centroid, boundary)
  384. :param points: tuple of points ((x, y), (x, y), ...)
  385. :return: tuple (number of added features, feature ids)
  386. """
  387. layer = self._getNewFeaturesLayer()
  388. cat = self._getNewFeaturesCat()
  389. if ftype == 'point':
  390. vtype = GV_POINT
  391. elif ftype == 'line':
  392. vtype = GV_LINE
  393. elif ftype == 'centroid':
  394. vtype = GV_CENTROID
  395. elif ftype == 'boundary':
  396. vtype = GV_BOUNDARY
  397. elif ftype == 'area':
  398. vtype = GV_AREA
  399. else:
  400. GError(parent=self.mapWindow,
  401. message=_("Unknown feature type '%s'") % ftype)
  402. return (-1, None)
  403. if vtype & GV_LINES and len(points) < 2:
  404. GError(parent=self.mapWindow,
  405. message=_("Not enough points for line"))
  406. return (-1, None)
  407. self.toolbar.EnableUndo()
  408. ret = self._addFeature(
  409. vtype, points, layer, cat, self._getSnapMode(),
  410. self._display.GetThreshold())
  411. if ret[0] > -1 and self.emit_signals:
  412. self.featureAdded.emit(new_bboxs=[self._createBbox(points)],
  413. new_areas_cats=[[{layer: [cat]}, None]])
  414. return ret
  415. def DeleteSelectedLines(self):
  416. """Delete selected features
  417. :return: number of deleted features
  418. """
  419. if not self._checkMap():
  420. return -1
  421. # collect categories for deleting if requested
  422. deleteRec = UserSettings.Get(
  423. group='vdigit',
  424. key='delRecord',
  425. subkey='enabled')
  426. catDict = dict()
  427. old_bboxs = []
  428. old_areas_cats = []
  429. if deleteRec:
  430. for i in self._display.selected['ids']:
  431. if Vect_read_line(self.poMapInfo, None, self.poCats, i) < 0:
  432. self._error.ReadLine(i)
  433. if self.emit_signals:
  434. ret = self._getLineAreaBboxCats(i)
  435. if ret:
  436. old_bboxs += ret[0]
  437. old_areas_cats += ret[1]
  438. # catDict was not used -> put into comment
  439. #cats = self.poCats.contents
  440. # for j in range(cats.n_cats):
  441. # if cats.field[j] not in catDict.keys():
  442. # catDict[cats.field[j]] = list()
  443. # catDict[cats.field[j]].append(cats.cat[j])
  444. poList = self._display.GetSelectedIList()
  445. nlines = Vedit_delete_lines(self.poMapInfo, poList)
  446. Vect_destroy_list(poList)
  447. self._display.selected['ids'] = list()
  448. if nlines > 0:
  449. if deleteRec:
  450. self._deleteRecords(catDict)
  451. self._addChangeset()
  452. self.toolbar.EnableUndo()
  453. if self.emit_signals:
  454. self.featuresDeleted.emit(old_bboxs=old_bboxs,
  455. old_areas_cats=old_areas_cats)
  456. return nlines
  457. def _deleteRecords(self, cats):
  458. """Delete records from attribute table
  459. :param cats: directory field/list of cats
  460. """
  461. handle = dbHandle()
  462. poHandle = pointer(handle)
  463. stmt = dbString()
  464. poStmt = pointer(stmt)
  465. for dblink in range(Vect_get_num_dblinks(self.poMapInfo)):
  466. poFi = Vect_get_dblink(self.poMapInfo, dblink)
  467. if poFi is None:
  468. self._error.DbLink(dblink)
  469. return -1
  470. Fi = poFi.contents
  471. if Fi.number not in cats.keys():
  472. continue
  473. poDriver = db_start_driver(Fi.driver)
  474. if poDriver is None:
  475. self._error.Driver(Fi.driver)
  476. return -1
  477. db_init_handle(poHandle)
  478. db_set_handle(poHandle, Fi.database, None)
  479. if db_open_database(poDriver, poHandle) != DB_OK:
  480. self._error.Database(Fi.driver, Fi.database)
  481. return -1
  482. db_init_string(poStmt)
  483. db_set_string(poStmt, "DELETE FROM %s WHERE" % Fi.table)
  484. n_cats = 0
  485. for cat in cats[Fi.number]:
  486. if n_cats > 0:
  487. db_append_string(poStmt, " or")
  488. db_append_string(poStmt, " %s = %d" % (Fi.key, cat))
  489. n_cats += 1
  490. if n_cats > 0 and \
  491. db_execute_immediate(poDriver, poStmt) != DB_OK:
  492. self._error.DbExecute(db_get_string(poStmt))
  493. return -1
  494. db_close_database_shutdown_driver(poDriver)
  495. def DeleteSelectedAreas(self):
  496. """Delete selected areas (centroid+boundaries)
  497. :return: number of deleted
  498. """
  499. if len(self._display.selected['ids']) < 1:
  500. return 0
  501. poList = self._display.GetSelectedIList()
  502. cList = poList.contents
  503. nareas = 0
  504. old_bboxs = []
  505. old_areas_cats = []
  506. for i in range(cList.n_values):
  507. if Vect_get_line_type(self.poMapInfo, cList.value[
  508. i]) != GV_CENTROID:
  509. continue
  510. if self.emit_signals:
  511. area = Vect_get_centroid_area(self.poMapInfo, cList.value[i])
  512. if area > 0:
  513. bbox, cats = self._getaAreaBboxCats(area)
  514. old_bboxs += bbox
  515. old_areas_cats += cats
  516. nareas += Vedit_delete_area_centroid(
  517. self.poMapInfo, cList.value[i])
  518. if nareas > 0:
  519. self._addChangeset()
  520. self.toolbar.EnableUndo()
  521. if self.emit_signals:
  522. self.areasDeleted.emit(old_bboxs=old_bboxs,
  523. old_areas_cats=old_areas_cats)
  524. return nareas
  525. def _getLineAreaBboxCats(self, ln_id):
  526. """Helper function
  527. :param ln_id: id of feature
  528. :return: None if the feature does not exists
  529. :return: list of :func:`_getaAreaBboxCats`
  530. """
  531. ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
  532. if ltype == GV_CENTROID:
  533. # TODO centroid opttimization, can be edited also its area -> it
  534. # will appear two times in new_ lists
  535. return self._getCentroidAreaBboxCats(ln_id)
  536. else:
  537. return [self._getBbox(ln_id)], [
  538. self._getLineAreasCategories(ln_id)]
  539. def _getCentroidAreaBboxCats(self, centroid):
  540. """Helper function
  541. :param centroid: id of an centroid
  542. :return: None if area does not exists
  543. :return: see return of :func:`_getaAreaBboxCats`
  544. """
  545. if not Vect_line_alive(self.poMapInfo, centroid):
  546. return None
  547. area = Vect_get_centroid_area(self.poMapInfo, centroid)
  548. if area > 0:
  549. return self._getaAreaBboxCats(area)
  550. else:
  551. return None
  552. def _getaAreaBboxCats(self, area):
  553. """Helper function
  554. :param area: area id
  555. :return: list of categories :func:`_getLineAreasCategories` and
  556. list of bboxes :func:`_getBbox` of area boundary
  557. features
  558. """
  559. po_b_list = Vect_new_list()
  560. Vect_get_area_boundaries(self.poMapInfo, area, po_b_list)
  561. b_list = po_b_list.contents
  562. geoms = []
  563. areas_cats = []
  564. if b_list.n_values > 0:
  565. for i_line in range(b_list.n_values):
  566. line = b_list.value[i_line]
  567. geoms.append(self._getBbox(abs(line)))
  568. areas_cats.append(self._getLineAreasCategories(abs(line)))
  569. Vect_destroy_list(po_b_list)
  570. return geoms, areas_cats
  571. def _getLineAreasCategories(self, ln_id):
  572. """Helper function
  573. :param line_id: id of boundary feature
  574. :return: categories of areas on the left, right side of the feature
  575. :return: format: [[{layer : [cat]}, None]] means:
  576. area to the left (list of layers which has cats list as values),
  577. area to the right (no area there in this case (None))
  578. :return: [] the feature is not boundary or does not exists
  579. """
  580. if not Vect_line_alive(self.poMapInfo, ln_id):
  581. return []
  582. ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
  583. if ltype != GV_BOUNDARY:
  584. return []
  585. cats = [None, None]
  586. left = c_int()
  587. right = c_int()
  588. if Vect_get_line_areas(
  589. self.poMapInfo, ln_id, pointer(left),
  590. pointer(right)) == 1:
  591. areas = [left.value, right.value]
  592. for i, a in enumerate(areas):
  593. if a > 0:
  594. centroid = Vect_get_area_centroid(self.poMapInfo, a)
  595. if centroid <= 0:
  596. continue
  597. c = self._getCategories(centroid)
  598. if c:
  599. cats[i] = c
  600. return cats
  601. def _getCategories(self, ln_id):
  602. """Helper function
  603. :param line_id: id of feature
  604. :return: list of the feature categories [{layer : cats}, next layer...]
  605. :return: None feature does not exist
  606. """
  607. if not Vect_line_alive(self.poMapInfo, ln_id):
  608. return none
  609. poCats = Vect_new_cats_struct()
  610. if Vect_read_line(self.poMapInfo, None, poCats, ln_id) < 0:
  611. Vect_destroy_cats_struct(poCats)
  612. return None
  613. cCats = poCats.contents
  614. cats = {}
  615. for j in range(cCats.n_cats):
  616. if cCats.field[j] in cats:
  617. cats[cCats.field[j]].append(cCats.cat[j])
  618. else:
  619. cats[cCats.field[j]] = [cCats.cat[j]]
  620. Vect_destroy_cats_struct(poCats)
  621. return cats
  622. def _getBbox(self, ln_id):
  623. """Helper function
  624. :param line_id: id of line feature
  625. :return: bbox bounding box of the feature
  626. :return: None feature does not exist
  627. """
  628. if not Vect_line_alive(self.poMapInfo, ln_id):
  629. return None
  630. poPoints = Vect_new_line_struct()
  631. if Vect_read_line(self.poMapInfo, poPoints, None, ln_id) < 0:
  632. Vect_destroy_line_struct(poPoints)
  633. return []
  634. geom = self._convertGeom(poPoints)
  635. bbox = self._createBbox(geom)
  636. Vect_destroy_line_struct(poPoints)
  637. return bbox
  638. def _createBbox(self, points):
  639. """Helper function
  640. :param points: list of points [(x, y), ...] to be bbox created for
  641. :return: bbox bounding box of points {'maxx':, 'maxy':, 'minx':, 'miny'}
  642. """
  643. bbox = {}
  644. for pt in points:
  645. if 'maxy' not in bbox:
  646. bbox['maxy'] = pt[1]
  647. bbox['miny'] = pt[1]
  648. bbox['maxx'] = pt[0]
  649. bbox['minx'] = pt[0]
  650. continue
  651. if bbox['maxy'] < pt[1]:
  652. bbox['maxy'] = pt[1]
  653. elif bbox['miny'] > pt[1]:
  654. bbox['miny'] = pt[1]
  655. if bbox['maxx'] < pt[0]:
  656. bbox['maxx'] = pt[0]
  657. elif bbox['minx'] > pt[0]:
  658. bbox['minx'] = pt[0]
  659. return bbox
  660. def _convertGeom(self, poPoints):
  661. """Helper function convert geom from ctypes line_pts to python
  662. list
  663. :return: coords in python list [(x, y),...]
  664. """
  665. Points = poPoints.contents
  666. pts_geom = []
  667. for j in range(Points.n_points):
  668. pts_geom.append((Points.x[j], Points.y[j]))
  669. return pts_geom
  670. def MoveSelectedLines(self, move):
  671. """Move selected features
  672. :param move: direction (x, y)
  673. """
  674. if not self._checkMap():
  675. return -1
  676. nsel = len(self._display.selected['ids'])
  677. if nsel < 1:
  678. return -1
  679. thresh = self._display.GetThreshold()
  680. snap = self._getSnapMode()
  681. poList = self._display.GetSelectedIList()
  682. if self.emit_signals:
  683. old_bboxs = []
  684. old_areas_cats = []
  685. for sel_id in self._display.selected['ids']:
  686. ret = self._getLineAreaBboxCats(sel_id)
  687. if ret:
  688. old_bboxs += ret[0]
  689. old_areas_cats += ret[1]
  690. Vect_set_updated(self.poMapInfo, 1)
  691. n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
  692. nlines = Vedit_move_lines(
  693. self.poMapInfo, self.popoBgMapInfo,
  694. int(self.poBgMapInfo is not None),
  695. poList, move[0],
  696. move[1],
  697. 0, snap, thresh)
  698. Vect_destroy_list(poList)
  699. if nlines > 0 and self.emit_signals:
  700. new_bboxs = []
  701. new_areas_cats = []
  702. n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
  703. for i in range(n_up_lines_old, n_up_lines):
  704. new_id = Vect_get_updated_line(self.poMapInfo, i)
  705. ret = self._getLineAreaBboxCats(new_id)
  706. if ret:
  707. new_bboxs += ret[0]
  708. new_areas_cats += ret[1]
  709. if nlines > 0 and self._settings['breakLines']:
  710. for i in range(1, nlines):
  711. self._breakLineAtIntersection(nlines + i, None, changeset)
  712. if nlines > 0:
  713. self._addChangeset()
  714. self.toolbar.EnableUndo()
  715. if self.emit_signals:
  716. self.featuresMoved.emit(new_bboxs=new_bboxs,
  717. old_bboxs=old_bboxs,
  718. old_areas_cats=old_areas_cats,
  719. new_areas_cats=new_areas_cats)
  720. return nlines
  721. def MoveSelectedVertex(self, point, move):
  722. """Move selected vertex of the line
  723. :param point: location point
  724. :param move: x,y direction
  725. :return: id of new feature
  726. :return: 0 vertex not moved (not found, line is not selected)
  727. :return: -1 on error
  728. """
  729. if not self._checkMap():
  730. return -1
  731. if len(self._display.selected['ids']) != 1:
  732. return -1
  733. # move only first found vertex in bbox
  734. poList = self._display.GetSelectedIList()
  735. if self.emit_signals:
  736. cList = poList.contents
  737. old_bboxs = [self._getBbox(cList.value[0])]
  738. old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
  739. Vect_set_updated(self.poMapInfo, 1)
  740. n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
  741. Vect_reset_line(self.poPoints)
  742. Vect_append_point(self.poPoints, point[0], point[1], 0.0)
  743. moved = Vedit_move_vertex(
  744. self.poMapInfo, self.popoBgMapInfo,
  745. int(self.poBgMapInfo is not None),
  746. poList, self.poPoints, self._display.GetThreshold(
  747. type='selectThresh'),
  748. self._display.GetThreshold(),
  749. move[0],
  750. move[1],
  751. 0.0, 1, self._getSnapMode())
  752. Vect_destroy_list(poList)
  753. if moved > 0 and self.emit_signals:
  754. n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
  755. new_bboxs = []
  756. new_areas_cats = []
  757. for i in range(n_up_lines_old, n_up_lines):
  758. new_id = Vect_get_updated_line(self.poMapInfo, i)
  759. new_bboxs.append(self._getBbox(new_id))
  760. new_areas_cats.append(self._getLineAreasCategories(new_id))
  761. if moved > 0 and self._settings['breakLines']:
  762. self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
  763. None)
  764. if moved > 0:
  765. self._addChangeset()
  766. self.toolbar.EnableUndo()
  767. if self.emit_signals:
  768. self.vertexMoved.emit(new_bboxs=new_bboxs,
  769. new_areas_cats=new_areas_cats,
  770. old_areas_cats=old_areas_cats,
  771. old_bboxs=old_bboxs)
  772. return moved
  773. def AddVertex(self, coords):
  774. """Add new vertex to the selected line/boundary on position 'coords'
  775. :param coords: coordinates to add vertex
  776. :return: id of new feature
  777. :return: 0 nothing changed
  778. :return: -1 on failure
  779. """
  780. added = self._ModifyLineVertex(coords, add=True)
  781. if added > 0:
  782. self.toolbar.EnableUndo()
  783. return added
  784. def RemoveVertex(self, coords):
  785. """Remove vertex from the selected line/boundary on position 'coords'
  786. :param coords: coordinates to remove vertex
  787. :return: id of new feature
  788. :return: 0 nothing changed
  789. :return: -1 on failure
  790. """
  791. deleted = self._ModifyLineVertex(coords, add=False)
  792. if deleted > 0:
  793. self.toolbar.EnableUndo()
  794. return deleted
  795. def SplitLine(self, point):
  796. """Split/break selected line/boundary on given position
  797. :param point: point where to split line
  798. :return: 1 line modified
  799. :return: 0 nothing changed
  800. :return: -1 error
  801. """
  802. thresh = self._display.GetThreshold('selectThresh')
  803. if not self._checkMap():
  804. return -1
  805. poList = self._display.GetSelectedIList()
  806. Vect_reset_line(self.poPoints)
  807. Vect_append_point(self.poPoints, point[0], point[1], 0.0)
  808. ret = Vedit_split_lines(self.poMapInfo, poList,
  809. self.poPoints, thresh, None)
  810. Vect_destroy_list(poList)
  811. if ret > 0:
  812. self._addChangeset()
  813. self.toolbar.EnableUndo()
  814. return ret
  815. def EditLine(self, line, coords):
  816. """Edit existing line/boundary
  817. :param line: feature id to be modified
  818. :param coords: list of coordinates of modified line
  819. :return: feature id of new line
  820. :return: -1 on error
  821. """
  822. if not self._checkMap():
  823. return -1
  824. if len(coords) < 2:
  825. self.DeleteSelectedLines()
  826. return 0
  827. if not Vect_line_alive(self.poMapInfo, line):
  828. self._error.DeadLine(line)
  829. return -1
  830. # read original feature
  831. ltype = Vect_read_line(self.poMapInfo, None, self.poCats, line)
  832. if ltype < 0:
  833. self._error.ReadLine(line)
  834. return -1
  835. if self.emit_signals:
  836. old_bboxs = [self._getBbox(line)]
  837. old_areas_cats = [self._getLineAreasCategories(line)]
  838. # build feature geometry
  839. Vect_reset_line(self.poPoints)
  840. for p in coords:
  841. Vect_append_point(self.poPoints, p[0], p[1], 0.0)
  842. # apply snapping (node or vertex)
  843. snap = self._getSnapMode()
  844. if snap != NO_SNAP:
  845. modeSnap = not (snap == SNAP)
  846. Vedit_snap_line(self.poMapInfo, self.popoBgMapInfo,
  847. int(self.poBgMapInfo is not None),
  848. -1, self.poPoints, self._display.GetThreshold(), modeSnap)
  849. newline = Vect_rewrite_line(self.poMapInfo, line, ltype,
  850. self.poPoints, self.poCats)
  851. if newline > 0 and self.emit_signals:
  852. new_geom = [self._getBbox(newline)]
  853. new_areas_cats = [self._getLineAreasCategories(newline)]
  854. if newline > 0 and self._settings['breakLines']:
  855. self._breakLineAtIntersection(newline, None)
  856. if newline > 0:
  857. self._addChangeset()
  858. self.toolbar.EnableUndo()
  859. if self.emit_signals:
  860. self.lineEdited.emit(old_bboxs=old_bboxs,
  861. old_areas_cats=old_areas_cats,
  862. new_bboxs=new_bboxs,
  863. new_areas_cats=new_areas_cats)
  864. return newline
  865. def FlipLine(self):
  866. """Flip selected lines/boundaries
  867. :return: number of modified lines
  868. :return: -1 on error
  869. """
  870. if not self._checkMap():
  871. return -1
  872. nlines = Vect_get_num_lines(self.poMapInfo)
  873. poList = self._display.GetSelectedIList()
  874. ret = Vedit_flip_lines(self.poMapInfo, poList)
  875. Vect_destroy_list(poList)
  876. if ret > 0:
  877. self._addChangeset()
  878. self.toolbar.EnableUndo()
  879. return ret
  880. def MergeLine(self):
  881. """Merge selected lines/boundaries
  882. :return: number of modified lines
  883. :return: -1 on error
  884. """
  885. if not self._checkMap():
  886. return -1
  887. poList = self._display.GetSelectedIList()
  888. ret = Vedit_merge_lines(self.poMapInfo, poList)
  889. Vect_destroy_list(poList)
  890. if ret > 0:
  891. self._addChangeset()
  892. self.toolbar.EnableUndo()
  893. return ret
  894. def BreakLine(self):
  895. """Break selected lines/boundaries
  896. :return: number of modified lines
  897. :return: -1 on error
  898. """
  899. if not self._checkMap():
  900. return -1
  901. poList = self._display.GetSelectedIList()
  902. ret = Vect_break_lines_list(self.poMapInfo, poList, None,
  903. GV_LINES, None)
  904. Vect_destroy_list(poList)
  905. if ret > 0:
  906. self._addChangeset()
  907. self.toolbar.EnableUndo()
  908. return ret
  909. def SnapLine(self):
  910. """Snap selected lines/boundaries
  911. :return: on success
  912. :return: -1 on error
  913. """
  914. if not self._checkMap():
  915. return -1
  916. nlines = Vect_get_num_lines(self.poMapInfo)
  917. poList = self._display.GetSelectedIList()
  918. Vect_snap_lines_list(self.poMapInfo, poList,
  919. self._display.GetThreshold(), None)
  920. Vect_destroy_list(poList)
  921. if nlines < Vect_get_num_lines(self.poMapInfo):
  922. self._addChangeset()
  923. self.toolbar.EnableUndo()
  924. def ConnectLine(self):
  925. """Connect selected lines/boundaries
  926. :return: 1 lines connected
  927. :return: 0 lines not connected
  928. :return: -1 on error
  929. """
  930. if not self._checkMap():
  931. return -1
  932. poList = self._display.GetSelectedIList()
  933. ret = Vedit_connect_lines(self.poMapInfo, poList,
  934. self._display.GetThreshold())
  935. Vect_destroy_list(poList)
  936. if ret > 0:
  937. self._addChangeset()
  938. self.toolbar.EnableUndo()
  939. return ret
  940. def CopyLine(self, ids=[]):
  941. """Copy features from (background) vector map
  942. :param ids: list of line ids to be copied
  943. :return: number of copied features
  944. :return: -1 on error
  945. """
  946. if not self._checkMap():
  947. return -1
  948. nlines = Vect_get_num_lines(self.poMapInfo)
  949. poList = self._display.GetSelectedIList(ids)
  950. ret = Vedit_copy_lines(self.poMapInfo, self.poBgMapInfo,
  951. poList)
  952. Vect_destroy_list(poList)
  953. if ret > 0 and self.poBgMapInfo and self._settings['breakLines']:
  954. for i in range(1, ret):
  955. self._breakLineAtIntersection(nlines + i, None)
  956. if ret > 0:
  957. self._addChangeset()
  958. self.toolbar.EnableUndo()
  959. return ret
  960. def CopyCats(self, fromId, toId, copyAttrb=False):
  961. """Copy given categories to objects with id listed in ids
  962. :param cats: ids of 'from' feature
  963. :param ids: ids of 'to' feature(s)
  964. :return: number of modified features
  965. :return: -1 on error
  966. """
  967. if len(fromId) < 1 or len(toId) < 1:
  968. return 0
  969. poCatsFrom = self.poCats
  970. poCatsTo = Vect_new_cats_struct()
  971. nlines = 0
  972. for fline in fromId:
  973. if not Vect_line_alive(self.poMapInfo, fline):
  974. continue
  975. if Vect_read_line(self.poMapInfo, None, poCatsFrom, fline) < 0:
  976. self._error.ReadLine(fline)
  977. return -1
  978. for tline in toId:
  979. if not Vect_line_alive(self.poMapInfo, tline):
  980. continue
  981. ltype = Vect_read_line(
  982. self.poMapInfo, self.poPoints, poCatsTo, tline)
  983. if ltype < 0:
  984. self._error.ReadLine(fline)
  985. return -1
  986. catsFrom = poCatsFrom.contents
  987. for i in range(catsFrom.n_cats):
  988. if not copyAttrb:
  989. # duplicate category
  990. cat = catsFrom.cat[i]
  991. else:
  992. # duplicate attributes
  993. cat = self.cats[catsFrom.field[i]] + 1
  994. self.cats[catsFrom.field[i]] = cat
  995. poFi = Vect_get_field(
  996. self.poMapInfo, catsFrom.field[i])
  997. if not poFi:
  998. self._error.DbLink(i)
  999. return -1
  1000. fi = poFi.contents
  1001. driver = db_start_driver(fi.driver)
  1002. if not driver:
  1003. self._error.Driver(fi.driver)
  1004. return -1
  1005. handle = dbHandle()
  1006. db_init_handle(byref(handle))
  1007. db_set_handle(byref(handle), fi.database, None)
  1008. if db_open_database(driver, byref(handle)) != DB_OK:
  1009. db_shutdown_driver(driver)
  1010. self._error.Database(fi.driver, fi.database)
  1011. return -1
  1012. stmt = dbString()
  1013. db_init_string(byref(stmt))
  1014. db_set_string(
  1015. byref(stmt), "SELECT * FROM %s WHERE %s=%d" %
  1016. (fi.table, fi.key, catsFrom.cat[i]))
  1017. cursor = dbCursor()
  1018. if db_open_select_cursor(
  1019. driver, byref(stmt),
  1020. byref(cursor),
  1021. DB_SEQUENTIAL) != DB_OK:
  1022. db_close_database_shutdown_driver(driver)
  1023. return -1
  1024. table = db_get_cursor_table(byref(cursor))
  1025. ncols = db_get_table_number_of_columns(table)
  1026. sql = "INSERT INTO %s VALUES (" % fi.table
  1027. # fetch the data
  1028. more = c_int()
  1029. while True:
  1030. if db_fetch(byref(cursor), DB_NEXT,
  1031. byref(more)) != DB_OK:
  1032. db_close_database_shutdown_driver(driver)
  1033. return -1
  1034. if not more.value:
  1035. break
  1036. value_string = dbString()
  1037. for col in range(ncols):
  1038. if col > 0:
  1039. sql += ","
  1040. column = db_get_table_column(table, col)
  1041. if db_get_column_name(column) == fi.key:
  1042. sql += "%d" % cat
  1043. continue
  1044. value = db_get_column_value(column)
  1045. db_convert_column_value_to_string(
  1046. column, byref(value_string))
  1047. if db_test_value_isnull(value):
  1048. sql += "NULL"
  1049. else:
  1050. ctype = db_sqltype_to_Ctype(
  1051. db_get_column_sqltype(column))
  1052. if ctype != DB_C_TYPE_STRING:
  1053. sql += db_get_string(byref(value_string))
  1054. else:
  1055. sql += "'%s'" % db_get_string(
  1056. byref(value_string))
  1057. sql += ")"
  1058. db_set_string(byref(stmt), sql)
  1059. if db_execute_immediate(driver, byref(stmt)) != DB_OK:
  1060. db_close_database_shutdown_driver(driver)
  1061. return -1
  1062. db_close_database_shutdown_driver(driver)
  1063. G_free(poFi)
  1064. if Vect_cat_set(poCatsTo, catsFrom.field[i], cat) < 1:
  1065. continue
  1066. if Vect_rewrite_line(self.poMapInfo, tline,
  1067. ltype, self.poPoints, poCatsTo) < 0:
  1068. self._error.WriteLine()
  1069. return -1
  1070. nlines += 1
  1071. Vect_destroy_cats_struct(poCatsTo)
  1072. if nlines > 0:
  1073. self.toolbar.EnableUndo()
  1074. return nlines
  1075. def _selectLinesByQueryThresh(self):
  1076. """Generic method used for SelectLinesByQuery() -- to get
  1077. threshold value"""
  1078. thresh = 0.0
  1079. if UserSettings.Get(group='vdigit', key='query',
  1080. subkey='selection') == 0:
  1081. thresh = UserSettings.Get(
  1082. group='vdigit', key='queryLength', subkey='thresh')
  1083. if UserSettings.Get(group='vdigit', key="queryLength",
  1084. subkey='than-selection') == 0:
  1085. thresh = -1 * thresh
  1086. else:
  1087. thresh = UserSettings.Get(
  1088. group='vdigit', key='queryDangle', subkey='thresh')
  1089. if UserSettings.Get(group='vdigit', key="queryDangle",
  1090. subkey='than-selection') == 0:
  1091. thresh = -1 * thresh
  1092. return thresh
  1093. def SelectLinesByQuery(self, bbox):
  1094. """Select features by query
  1095. .. todo::
  1096. layer / 3D
  1097. :param bbox: bounding box definition
  1098. """
  1099. if not self._checkMap():
  1100. return -1
  1101. thresh = self._selectLinesByQueryThresh()
  1102. query = QUERY_UNKNOWN
  1103. if UserSettings.Get(group='vdigit', key='query',
  1104. subkey='selection') == 0:
  1105. query = QUERY_LENGTH
  1106. else:
  1107. query = QUERY_DANGLE
  1108. ftype = GV_POINTS | GV_LINES # TODO: 3D
  1109. layer = 1 # TODO
  1110. ids = list()
  1111. poList = Vect_new_list()
  1112. coList = poList.contents
  1113. if UserSettings.Get(group='vdigit', key='query', subkey='box'):
  1114. Vect_reset_line(self.poPoints)
  1115. x1, y1 = bbox[0]
  1116. x2, y2 = bbox[1]
  1117. z1 = z2 = 0.0
  1118. Vect_append_point(self.poPoints, x1, y1, z1)
  1119. Vect_append_point(self.poPoints, x2, y1, z2)
  1120. Vect_append_point(self.poPoints, x2, y2, z1)
  1121. Vect_append_point(self.poPoints, x1, y2, z2)
  1122. Vect_append_point(self.poPoints, x1, y1, z1)
  1123. Vect_select_lines_by_polygon(
  1124. self.poMapInfo, self.poPoints, 0, None, ftype, poList)
  1125. if coList.n_values == 0:
  1126. return ids
  1127. Vedit_select_by_query(self.poMapInfo,
  1128. ftype, layer, thresh, query,
  1129. poList)
  1130. for i in range(coList.n_values):
  1131. ids.append(int(coList.value[i]))
  1132. Debug.msg(3, "IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values)
  1133. Vect_destroy_list(poList)
  1134. return ids
  1135. def IsVector3D(self):
  1136. """Check if open vector map is 3D
  1137. """
  1138. if not self._checkMap():
  1139. return False
  1140. return Vect_is_3d(self.poMapInfo)
  1141. def GetLineLength(self, line):
  1142. """Get line length
  1143. :param line: feature id
  1144. :return: line length
  1145. :return: -1 on error
  1146. """
  1147. if not self._checkMap():
  1148. return -1
  1149. if not Vect_line_alive(self.poMapInfo, line):
  1150. return -1
  1151. ltype = Vect_read_line(self.poMapInfo, self.poPoints, None, line)
  1152. if ltype < 0:
  1153. self._error.ReadLine(line)
  1154. return ret
  1155. length = -1
  1156. if ltype & GV_LINES: # lines & boundaries
  1157. length = Vect_line_length(self.poPoints)
  1158. return length
  1159. def GetAreaSize(self, centroid):
  1160. """Get area size
  1161. :param centroid: centroid id
  1162. :return: area size
  1163. :return: -1 on error
  1164. """
  1165. if not self._checkMap():
  1166. return -1
  1167. ltype = Vect_read_line(self.poMapInfo, None, None, centroid)
  1168. if ltype < 0:
  1169. self._error.ReadLine(line)
  1170. return ret
  1171. if ltype != GV_CENTROID:
  1172. return -1
  1173. area = Vect_get_centroid_area(self.poMapInfo, centroid)
  1174. size = -1
  1175. if area > 0:
  1176. if not Vect_area_alive(self.poMapInfo, area):
  1177. return size
  1178. size = Vect_get_area_area(self.poMapInfo, area)
  1179. return size
  1180. def GetAreaPerimeter(self, centroid):
  1181. """Get area perimeter
  1182. :param centroid: centroid id
  1183. :return: area size
  1184. :return: -1 on error
  1185. """
  1186. if not self._checkMap():
  1187. return -1
  1188. ltype = Vect_read_line(self.poMapInfo, None, None, centroid)
  1189. if ltype < 0:
  1190. self._error.ReadLine(line)
  1191. return ret
  1192. if ltype != GV_CENTROID:
  1193. return -1
  1194. area = Vect_get_centroid_area(self.poMapInfo, centroid)
  1195. perimeter = -1
  1196. if area > 0:
  1197. if not Vect_area_alive(self.poMapInfo, area):
  1198. return -1
  1199. Vect_get_area_points(self.poMapInfo, area, self.poPoints)
  1200. perimeter = Vect_area_perimeter(self.poPoints)
  1201. return perimeter
  1202. def SetLineCats(self, line, layer, cats, add=True):
  1203. """Set categories for given line and layer
  1204. :param line: feature id
  1205. :param layer: layer number (-1 for first selected line)
  1206. :param cats: list of categories
  1207. :param add: if True to add, otherwise do delete categories
  1208. :return: new feature id (feature need to be rewritten)
  1209. :return: -1 on error
  1210. """
  1211. if not self._checkMap():
  1212. return -1
  1213. if line < 1 and len(self._display.selected['ids']) < 1:
  1214. return -1
  1215. update = False
  1216. if line == -1:
  1217. update = True
  1218. line = self._display.selected['ids'][0]
  1219. if not Vect_line_alive(self.poMapInfo, line):
  1220. return -1
  1221. ltype = Vect_read_line(
  1222. self.poMapInfo,
  1223. self.poPoints,
  1224. self.poCats,
  1225. line)
  1226. if ltype < 0:
  1227. self._error.ReadLine(line)
  1228. return -1
  1229. for c in cats:
  1230. if add:
  1231. Vect_cat_set(self.poCats, layer, c)
  1232. else:
  1233. Vect_field_cat_del(self.poCats, layer, c)
  1234. newline = Vect_rewrite_line(self.poMapInfo, line, ltype,
  1235. self.poPoints, self.poCats)
  1236. if newline > 0:
  1237. self._addChangeset()
  1238. self.toolbar.EnableUndo()
  1239. if update:
  1240. # update line id since the line was rewritten
  1241. self._display.selected['ids'][0] = newline
  1242. return newline
  1243. def TypeConvForSelectedLines(self):
  1244. """Feature type conversion for selected objects.
  1245. Supported conversions:
  1246. - point <-> centroid
  1247. - line <-> boundary
  1248. :return: number of modified features
  1249. :return: -1 on error
  1250. """
  1251. if not self._checkMap():
  1252. return -1
  1253. poList = self._display.GetSelectedIList()
  1254. ret = Vedit_chtype_lines(self.poMapInfo, poList)
  1255. Vect_destroy_list(poList)
  1256. if ret > 0:
  1257. self._addChangeset()
  1258. self.toolbar.EnableUndo()
  1259. return ret
  1260. def Undo(self, level=-1):
  1261. """Undo action
  1262. :param level: levels to undo (0 to revert all)
  1263. :return: id of current changeset
  1264. """
  1265. changesetLast = len(self.changesets) - 1
  1266. if changesetLast < 0:
  1267. return changesetLast
  1268. if level < 0 and self.changesetCurrent > changesetLast:
  1269. self.changesetCurrent = changesetLast
  1270. elif level == 0:
  1271. # 0 -> undo all
  1272. level = -1 * self.changesetCurrent - 1
  1273. Debug.msg(
  1274. 2,
  1275. "Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d",
  1276. changesetLast,
  1277. self.changesetCurrent,
  1278. level)
  1279. if level < 0: # undo
  1280. if self.changesetCurrent + level < -1:
  1281. return self.changesetCurrent
  1282. for changeset in range(self.changesetCurrent,
  1283. self.changesetCurrent + level, -1):
  1284. self._applyChangeset(changeset, undo=True)
  1285. elif level > 0: # redo
  1286. if self.changesetCurrent + 1 > changesetLast:
  1287. return self.changesetCurrent
  1288. for changeset in range(self.changesetCurrent + 1,
  1289. self.changesetCurrent + 1 + level):
  1290. self._applyChangeset(changeset, undo=False)
  1291. self.changesetCurrent += level
  1292. Debug.msg(2, "Digit.Undo(): changeset_current=%d, changeset_last=%d",
  1293. self.changesetCurrent, changesetLast)
  1294. self.mapWindow.UpdateMap(render=False)
  1295. if self.changesetCurrent < 0: # disable undo tool
  1296. self.toolbar.EnableUndo(False)
  1297. else:
  1298. self.toolbar.EnableUndo(True)
  1299. if self.changesetCurrent < changesetLast:
  1300. self.toolbar.EnableRedo(True)
  1301. else:
  1302. self.toolbar.EnableRedo(False)
  1303. def ZBulkLines(self, pos1, pos2, start, step):
  1304. """Z-bulk labeling
  1305. :param pos1: reference line (start point)
  1306. :param pos1: reference line (end point)
  1307. :param start: starting value
  1308. :param step: step value
  1309. :return: number of modified lines
  1310. :return: -1 on error
  1311. """
  1312. if not self._checkMap():
  1313. return -1
  1314. poList = self._display.GetSelectedIList()
  1315. ret = Vedit_bulk_labeling(self.poMapInfo, poList,
  1316. pos1[0], pos1[1], pos2[0], pos2[1],
  1317. start, step)
  1318. Vect_destroy_list(poList)
  1319. if ret > 0:
  1320. self._addChangeset()
  1321. self.toolbar.EnableUndo()
  1322. return ret
  1323. def GetDisplay(self):
  1324. """Get display driver instance"""
  1325. return self._display
  1326. def OpenMap(self, name, update=True, tmp=False):
  1327. """Open vector map for editing
  1328. :param map: name of vector map to be set up
  1329. :type map: str
  1330. :param tmp: True to open temporary vector map
  1331. """
  1332. Debug.msg(3, "AbstractDigit.SetMapName map=%s" % name)
  1333. if '@' in name:
  1334. name, mapset = name.split('@')
  1335. else:
  1336. mapset = grass.gisenv()['MAPSET']
  1337. self.poMapInfo = self._display.OpenMap(
  1338. str(name), str(mapset), update, tmp)
  1339. if self.poMapInfo:
  1340. self.InitCats()
  1341. return self.poMapInfo
  1342. def CloseMap(self):
  1343. """Close currently open vector map
  1344. """
  1345. if not self._checkMap():
  1346. return
  1347. # print extra line before building message
  1348. sys.stdout.write(os.linesep)
  1349. # build topology, close map
  1350. self._display.CloseMap()
  1351. def InitCats(self):
  1352. """Initialize categories information
  1353. :return: 0 on success
  1354. :return: -1 on error
  1355. """
  1356. self.cats.clear()
  1357. if not self._checkMap():
  1358. return -1
  1359. ndblinks = Vect_get_num_dblinks(self.poMapInfo)
  1360. for i in range(ndblinks):
  1361. fi = Vect_get_dblink(self.poMapInfo, i).contents
  1362. if fi:
  1363. self.cats[fi.number] = None
  1364. # find max category
  1365. nfields = Vect_cidx_get_num_fields(self.poMapInfo)
  1366. Debug.msg(2, "wxDigit.InitCats(): nfields=%d", nfields)
  1367. for i in range(nfields):
  1368. field = Vect_cidx_get_field_number(self.poMapInfo, i)
  1369. ncats = Vect_cidx_get_num_cats_by_index(self.poMapInfo, i)
  1370. if field <= 0:
  1371. continue
  1372. for j in range(ncats):
  1373. cat = c_int()
  1374. type = c_int()
  1375. id = c_int()
  1376. Vect_cidx_get_cat_by_index(self.poMapInfo, i, j,
  1377. byref(cat), byref(type), byref(id))
  1378. if field in self.cats:
  1379. if cat > self.cats[field]:
  1380. self.cats[field] = cat.value
  1381. else:
  1382. self.cats[field] = cat.value
  1383. Debug.msg(
  1384. 3,
  1385. "wxDigit.InitCats(): layer=%d, cat=%d",
  1386. field,
  1387. self.cats[field])
  1388. # set default values
  1389. for field, cat in self.cats.iteritems():
  1390. if cat is None:
  1391. self.cats[field] = 0 # first category 1
  1392. Debug.msg(
  1393. 3,
  1394. "wxDigit.InitCats(): layer=%d, cat=%d",
  1395. field,
  1396. self.cats[field])
  1397. def _checkMap(self):
  1398. """Check if map is open
  1399. """
  1400. if not self.poMapInfo:
  1401. self._error.NoMap()
  1402. return False
  1403. return True
  1404. def _addFeature(self, ftype, coords, layer, cat, snap, threshold):
  1405. """Add new feature(s) to the vector map
  1406. :param ftype: feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...)
  1407. :param coords: tuple of coordinates ((x, y), (x, y), ...)
  1408. :param layer: layer number (-1 for no cat)
  1409. :param cat: category number
  1410. :param snap: snap to node/vertex
  1411. :param threshold: threshold for snapping
  1412. :return: tuple (number of added features, list of fids)
  1413. :return: number of features -1 on error
  1414. """
  1415. fids = list()
  1416. if not self._checkMap():
  1417. return (-1, None)
  1418. is3D = bool(Vect_is_3d(self.poMapInfo))
  1419. Debug.msg(
  1420. 2,
  1421. "IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d",
  1422. len(coords),
  1423. layer,
  1424. cat,
  1425. snap)
  1426. if not (ftype & (GV_POINTS | GV_LINES | GV_AREA)): # TODO: 3D
  1427. self._error.FeatureType(ftype)
  1428. return (-1, None)
  1429. # set category
  1430. Vect_reset_cats(self.poCats)
  1431. if layer > 0 and ftype != GV_AREA:
  1432. Vect_cat_set(self.poCats, layer, cat)
  1433. self.cats[layer] = max(cat, self.cats.get(layer, 1))
  1434. # append points
  1435. Vect_reset_line(self.poPoints)
  1436. for c in coords:
  1437. Vect_append_point(self.poPoints, c[0], c[1], 0.0)
  1438. if ftype & (GV_BOUNDARY | GV_AREA):
  1439. # close boundary
  1440. points = self.poPoints.contents
  1441. last = points.n_points - 1
  1442. if self._settings['closeBoundary']:
  1443. Vect_append_point(
  1444. self.poPoints, points.x[0],
  1445. points.y[0],
  1446. points.z[0])
  1447. elif Vect_points_distance(points.x[0], points.y[0], points.z[0],
  1448. points.x[last], points.y[last], points.z[last],
  1449. is3D) <= threshold:
  1450. points.x[last] = points.x[0]
  1451. points.y[last] = points.y[0]
  1452. points.z[last] = points.z[0]
  1453. if snap != NO_SNAP:
  1454. # apply snapping (node or vertex)
  1455. modeSnap = not (snap == SNAP)
  1456. Vedit_snap_line(self.poMapInfo, self.popoBgMapInfo, int(
  1457. self.poBgMapInfo is not None), -1, self.poPoints, threshold, modeSnap)
  1458. if ftype == GV_AREA:
  1459. ltype = GV_BOUNDARY
  1460. else:
  1461. ltype = ftype
  1462. newline = Vect_write_line(
  1463. self.poMapInfo,
  1464. ltype,
  1465. self.poPoints,
  1466. self.poCats)
  1467. if newline < 0:
  1468. self._error.WriteLine()
  1469. return (-1, None)
  1470. fids.append(newline)
  1471. # add centroids for left/right area
  1472. if ftype & GV_AREA:
  1473. left = right = -1
  1474. bpoints = Vect_new_line_struct()
  1475. cleft = c_int()
  1476. cright = c_int()
  1477. Vect_get_line_areas(self.poMapInfo, newline,
  1478. byref(cleft), byref(cright))
  1479. left = cleft.value
  1480. right = cright.value
  1481. Debug.msg(3, "IVDigit._addFeature(): area - left=%d right=%d",
  1482. left, right)
  1483. # check if area exists and has no centroid inside
  1484. if layer > 0 and (left > 0 or right > 0):
  1485. Vect_cat_set(self.poCats, layer, cat)
  1486. self.cats[layer] = max(cat, self.cats.get(layer, 0))
  1487. x = c_double()
  1488. y = c_double()
  1489. if left > 0 and \
  1490. Vect_get_area_centroid(self.poMapInfo, left) == 0:
  1491. # if Vect_get_area_points(self.poMapInfo, left, bpoints) > 0 and
  1492. # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
  1493. if Vect_get_point_in_area(
  1494. self.poMapInfo, left, byref(x),
  1495. byref(y)) == 0:
  1496. Vect_reset_line(bpoints)
  1497. Vect_append_point(bpoints, x.value, y.value, 0.0)
  1498. newc = Vect_write_line(self.poMapInfo, GV_CENTROID,
  1499. bpoints, self.poCats)
  1500. if newc < 0:
  1501. self._error.WriteLine()
  1502. return (len(fids), fids)
  1503. else:
  1504. fids.append(newc)
  1505. if right > 0 and \
  1506. Vect_get_area_centroid(self.poMapInfo, right) == 0:
  1507. # if Vect_get_area_points(byref(self.poMapInfo), right, bpoints) > 0 and
  1508. # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0:
  1509. if Vect_get_point_in_area(
  1510. self.poMapInfo, right, byref(x),
  1511. byref(y)) == 0:
  1512. Vect_reset_line(bpoints)
  1513. Vect_append_point(bpoints, x.value, y.value, 0.0)
  1514. newc = Vect_write_line(self.poMapInfo, GV_CENTROID,
  1515. bpoints, self.poCats)
  1516. if newc < 0:
  1517. self._error.WriteLine()
  1518. return (len(fids, fids))
  1519. else:
  1520. fids.append(newc)
  1521. Vect_destroy_line_struct(bpoints)
  1522. # break line or boundary at intersection
  1523. if self._settings['breakLines']:
  1524. self._breakLineAtIntersection(newline, self.poPoints)
  1525. self._addChangeset()
  1526. if ftype & GV_AREA:
  1527. # len(fids) == 1 -> no new area
  1528. return (len(fids) - 1, fids)
  1529. return (len(fids), fids)
  1530. def _ModifyLineVertex(self, coords, add=True):
  1531. """Add or remove vertex
  1532. Shape of line/boundary is not changed when adding new vertex.
  1533. :param coords: coordinates of point
  1534. :param add: True to add, False to remove
  1535. :type add: bool
  1536. :return: 1 on success
  1537. :return: 0 nothing changed
  1538. :return: -1 error
  1539. """
  1540. if not self._checkMap():
  1541. return -1
  1542. selected = self._display.selected
  1543. if len(selected['ids']) != 1:
  1544. return 0
  1545. poList = self._display.GetSelectedIList()
  1546. if self.emit_signals:
  1547. cList = poList.contents
  1548. old_bboxs = [self._getBbox(cList.value[0])]
  1549. old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
  1550. Vect_set_updated(self.poMapInfo, 1)
  1551. n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
  1552. Vect_reset_line(self.poPoints)
  1553. Vect_append_point(self.poPoints, coords[0], coords[1], 0.0)
  1554. thresh = self._display.GetThreshold(type='selectThresh')
  1555. if add:
  1556. ret = Vedit_add_vertex(self.poMapInfo, poList,
  1557. self.poPoints, thresh)
  1558. else:
  1559. ret = Vedit_remove_vertex(self.poMapInfo, poList,
  1560. self.poPoints, thresh)
  1561. Vect_destroy_list(poList)
  1562. if ret > 0 and self.emit_signals:
  1563. new_bboxs = []
  1564. new_areas_cats = []
  1565. n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
  1566. for i in range(n_up_lines_old, n_up_lines):
  1567. new_id = Vect_get_updated_line(self.poMapInfo, i)
  1568. new_areas_cats.append(self._getLineAreasCategories(new_id))
  1569. new_bboxs.append(self._getBbox(new_id))
  1570. if not add and ret > 0 and self._settings['breakLines']:
  1571. self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
  1572. None)
  1573. if ret > 0:
  1574. self._addChangeset()
  1575. if ret > 0 and self.emit_signals:
  1576. if add:
  1577. self.vertexAdded.emit(old_bboxs=old_bboxs, new_bboxs=new_bboxs)
  1578. else:
  1579. self.vertexRemoved.emit(old_bboxs=old_bboxs,
  1580. new_bboxs=new_bboxs,
  1581. old_areas_cats=old_areas_cats,
  1582. new_areas_cats=new_areas_cats)
  1583. return 1
  1584. def GetLineCats(self, line):
  1585. """Get list of layer/category(ies) for selected feature.
  1586. :param line: feature id (-1 for first selected feature)
  1587. :return: list of layer/cats
  1588. """
  1589. ret = dict()
  1590. if not self._checkMap():
  1591. return ret
  1592. if line == -1 and len(self._display.selected['ids']) < 1:
  1593. return ret
  1594. if line == -1:
  1595. line = self._display.selected['ids'][0]
  1596. if not Vect_line_alive(self.poMapInfo, line):
  1597. self._error.DeadLine(line)
  1598. return ret
  1599. if Vect_read_line(self.poMapInfo, None, self.poCats, line) < 0:
  1600. self._error.ReadLine(line)
  1601. return ret
  1602. cats = self.poCats.contents
  1603. for i in range(cats.n_cats):
  1604. field = cats.field[i]
  1605. if field not in ret:
  1606. ret[field] = list()
  1607. ret[field].append(cats.cat[i])
  1608. return ret
  1609. def GetLayers(self):
  1610. """Get list of layers
  1611. Requires self.InitCats() to be called.
  1612. :return: list of layers
  1613. """
  1614. return self.cats.keys()
  1615. def UpdateSettings(self):
  1616. """Update digit (and display) settings
  1617. """
  1618. self._display.UpdateSettings()
  1619. self._settings['breakLines'] = bool(
  1620. UserSettings.Get(
  1621. group='vdigit',
  1622. key="breakLines",
  1623. subkey='enabled'))
  1624. self._settings['closeBoundary'] = bool(
  1625. UserSettings.Get(
  1626. group='vdigit',
  1627. key="closeBoundary",
  1628. subkey='enabled'))
  1629. def SetCategory(self):
  1630. """Update self.cats based on settings"""
  1631. sel = UserSettings.Get(
  1632. group='vdigit',
  1633. key='categoryMode',
  1634. subkey='selection')
  1635. cat = None
  1636. if sel == 0: # next to usep
  1637. cat = self._setCategoryNextToUse()
  1638. elif sel == 1:
  1639. cat = UserSettings.Get(
  1640. group='vdigit',
  1641. key='category',
  1642. subkey='value')
  1643. if cat:
  1644. layer = UserSettings.Get(
  1645. group='vdigit', key='layer', subkey='value')
  1646. self.cats[layer] = cat
  1647. return cat
  1648. def _setCategoryNextToUse(self):
  1649. """Find maximum category number for the given layer and
  1650. update the settings
  1651. :return: category to be used
  1652. """
  1653. # get max category number for given layer and update the settings
  1654. layer = UserSettings.Get(group='vdigit', key='layer', subkey='value')
  1655. cat = self.cats.get(layer, 0) + 1
  1656. UserSettings.Set(group='vdigit', key='category', subkey='value',
  1657. value=cat)
  1658. Debug.msg(1, "IVDigit._setCategoryNextToUse(): cat=%d", cat)
  1659. return cat
  1660. def SelectLinesFromBackgroundMap(self, bbox):
  1661. """Select features from background map
  1662. :param bbox: bounding box definition
  1663. :return: list of selected feature ids
  1664. """
  1665. # try select features by box first
  1666. if self._display.SelectLinesByBox(
  1667. bbox, poMapInfo=self.poBgMapInfo) < 1:
  1668. self._display.SelectLineByPoint(
  1669. bbox[0], poMapInfo=self.poBgMapInfo)['line']
  1670. return self._display.selected['ids']
  1671. def GetUndoLevel(self):
  1672. """Get undo level (number of active changesets)
  1673. Note: Changesets starts wiht 0
  1674. """
  1675. return self.changesetCurrent
  1676. def GetFeatureType(self):
  1677. """Get feature type for OGR layers
  1678. :return: feature type as string (point, linestring, polygon)
  1679. :return: None for native format
  1680. """
  1681. topoFormat = Vect_get_finfo_topology_info(
  1682. self.poMapInfo, None, None, None)
  1683. if topoFormat == GV_TOPO_PSEUDO:
  1684. return Vect_get_finfo_geometry_type(self.poMapInfo)
  1685. return ''