wxdigit.py 68 KB

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