wxdisplay.py 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207
  1. """
  2. @package vdigit.wxdisplay
  3. @brief wxGUI vector digitizer (display driver)
  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. - wxdisplay::DisplayDriver
  8. (C) 2007-2016 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Martin Landa <landa.martin gmail.com>
  12. """
  13. from __future__ import print_function
  14. import locale
  15. import six
  16. import wx
  17. from core.debug import Debug
  18. from core.settings import UserSettings
  19. from gui_core.wrap import Rect
  20. try:
  21. WindowsError
  22. except NameError:
  23. WindowsError = OSError
  24. try:
  25. from grass.lib.gis import *
  26. from grass.lib.vector import *
  27. from grass.lib.vedit import *
  28. except (ImportError, WindowsError) as e:
  29. print("wxdigit.py: {}".format(e), file=sys.stderr)
  30. log = None
  31. progress = None
  32. last_error = ''
  33. def print_error(msg, type):
  34. """Redirect stderr"""
  35. global log
  36. if log:
  37. log.write(msg + os.linesep)
  38. else:
  39. print(msg)
  40. global last_error
  41. last_error += ' ' + msg
  42. return 0
  43. def print_progress(value):
  44. """Redirect progress info"""
  45. global progress
  46. if progress:
  47. progress.SetValue(value)
  48. else:
  49. pass # discard progress info
  50. return 0
  51. def GetLastError():
  52. global last_error
  53. ret = last_error
  54. if ret[-1] != '.':
  55. ret += '.'
  56. last_error = '' # reset
  57. return ret
  58. try:
  59. errtype = CFUNCTYPE(UNCHECKED(c_int), String, c_int)
  60. errfunc = errtype(print_error)
  61. pertype = CFUNCTYPE(UNCHECKED(c_int), c_int)
  62. perfunc = pertype(print_progress)
  63. except NameError:
  64. pass
  65. class DisplayDriver:
  66. def __init__(self, device, deviceTmp, mapObj, window, glog, gprogress):
  67. """Display driver used by vector digitizer
  68. :param device: wx.PseudoDC device where to draw vector objects
  69. :param deviceTmp: wx.PseudoDC device where to draw temporary vector objects
  70. :param mapOng: Map Object (render.Map)
  71. :param windiow: parent window for dialogs
  72. :param glog: logging device (None to discard messages)
  73. :param gprogress: progress bar device (None to discard message)
  74. """
  75. global errfunc, perfunc, log, progress
  76. log = glog
  77. progress = gprogress
  78. G_gisinit('wxvdigit')
  79. locale.setlocale(locale.LC_NUMERIC, 'C')
  80. G_set_error_routine(errfunc)
  81. G_set_percent_routine(perfunc)
  82. # G_set_fatal_error(FATAL_RETURN)
  83. self.mapInfo = None # open vector map (Map_Info structure)
  84. self.poMapInfo = None # pointer to self.mapInfo
  85. self.is3D = False # is open vector map 3D
  86. self.dc = device # PseudoDC devices
  87. self.dcTmp = deviceTmp
  88. self.mapObj = mapObj
  89. self.region = mapObj.GetCurrentRegion()
  90. self.window = window
  91. self.log = log # log device
  92. self.firstNode = True # track PseudoDC Id of selected features
  93. self.lastNodeId = -1
  94. # GRASS lib
  95. self.poPoints = Vect_new_line_struct()
  96. self.poCats = Vect_new_cats_struct()
  97. # selected objects
  98. self.selected = {
  99. 'field': -1, # field number
  100. 'cats': list(), # list of cats
  101. 'ids': list(), # list of ids
  102. 'idsDupl': list(), # list of duplicated features
  103. }
  104. # digitizer settings
  105. self.settings = {
  106. 'highlight': None,
  107. 'highlightDupl': {'enabled': False,
  108. 'color': None},
  109. 'point': {'enabled': False,
  110. 'color': None},
  111. 'line': {'enabled': False,
  112. 'color': None},
  113. 'boundaryNo': {'enabled': False,
  114. 'color': None},
  115. 'boundaryOne': {'enabled': False,
  116. 'color': None},
  117. 'boundaryTwo': {'enabled': False,
  118. 'color': None},
  119. 'centroidIn': {'enabled': False,
  120. 'color': None},
  121. 'centroidOut': {'enabled': False,
  122. 'color': None},
  123. 'centroidDup': {'enabled': False,
  124. 'color': None},
  125. 'nodeOne': {'enabled': False,
  126. 'color': None},
  127. 'nodeTwo': {'enabled': False,
  128. 'color': None},
  129. 'vertex': {'enabled': False,
  130. 'color': None},
  131. 'area': {'enabled': False,
  132. 'color': None},
  133. 'direction': {'enabled': False,
  134. 'color': None},
  135. 'lineWidth': -1, # screen units
  136. }
  137. # topology
  138. self._resetTopology()
  139. self._drawSelected = False
  140. self._drawSegments = False
  141. self.UpdateSettings()
  142. def __del__(self):
  143. """Close currently open vector map"""
  144. G_unset_error_routine()
  145. G_unset_percent_routine()
  146. if self.poMapInfo:
  147. self.CloseMap()
  148. Vect_destroy_line_struct(self.poPoints)
  149. Vect_destroy_cats_struct(self.poCats)
  150. def _resetTopology(self):
  151. """Reset topology dict
  152. """
  153. self.topology = {
  154. 'highlight': 0,
  155. 'point': 0,
  156. 'line': 0,
  157. 'boundaryNo': 0,
  158. 'boundaryOne': 0,
  159. 'boundaryTwo': 0,
  160. 'centroidIn': 0,
  161. 'centroidOut': 0,
  162. 'centroidDup': 0,
  163. 'nodeOne': 0,
  164. 'nodeTwo': 0,
  165. 'vertex': 0,
  166. }
  167. def _cell2Pixel(self, east, north, elev):
  168. """Conversion from geographic coordinates (east, north)
  169. to screen (x, y)
  170. .. todo::
  171. 3D stuff...
  172. :param east: east coordinate
  173. :param north: north coordinate
  174. :param elev: elevation
  175. :return: x, y screen coordinates (integer)
  176. """
  177. map_res = max(self.region['ewres'], self.region['nsres'])
  178. w = self.region['center_easting'] - (self.mapObj.width / 2) * map_res
  179. n = self.region['center_northing'] + (self.mapObj.height / 2) * map_res
  180. return int((east - w) / map_res), int((n - north) / map_res)
  181. def _drawCross(self, pdc, point, size=5):
  182. """Draw cross symbol of given size to device content
  183. Used for points, nodes, vertices
  184. :param pdc: PseudoDC where to draw
  185. :param point: coordinates of center
  186. :param size: size of the cross symbol
  187. :return: 0 on success
  188. :return: -1 on failure
  189. """
  190. if not pdc or not point:
  191. return -1
  192. pdc.DrawLine(point.x - size, point.y, point.x + size, point.y)
  193. pdc.DrawLine(point.x, point.y - size, point.x, point.y + size)
  194. return 0
  195. def _drawObject(self, robj):
  196. """Draw given object to the device
  197. The object is defined as robject() from vedit.h.
  198. :param robj: object to be rendered
  199. :return: 1 on success
  200. :return: -1 on failure (vector feature marked as dead, etc.)
  201. """
  202. if not self.dc or not self.dcTmp:
  203. return -1
  204. Debug.msg(
  205. 4,
  206. "_drawObject(): line=%d type=%d npoints=%d",
  207. robj.fid,
  208. robj.type,
  209. robj.npoints)
  210. brush = None
  211. if robj.type == TYPE_AREA and self._isSelected(
  212. Vect_get_area_centroid(self.poMapInfo, robj.fid)):
  213. pdc = self.dcTmp
  214. pen = wx.TRANSPARENT_PEN
  215. brush = wx.TRANSPARENT_BRUSH
  216. dcId = 1
  217. self.topology['highlight'] += 1
  218. if not self._drawSelected:
  219. return
  220. elif robj.type != TYPE_AREA and self._isSelected(robj.fid):
  221. pdc = self.dcTmp
  222. if self.settings['highlightDupl'][
  223. 'enabled'] and self._isDuplicated(robj.fid):
  224. pen = wx.Pen(
  225. self.settings['highlightDupl']['color'],
  226. self.settings['lineWidth'],
  227. wx.SOLID)
  228. else:
  229. pen = wx.Pen(
  230. self.settings['highlight'],
  231. self.settings['lineWidth'],
  232. wx.SOLID)
  233. dcId = 1
  234. self.topology['highlight'] += 1
  235. if not self._drawSelected:
  236. return
  237. else:
  238. pdc = self.dc
  239. pen, brush = self._definePen(robj.type)
  240. dcId = 0
  241. pdc.SetPen(pen)
  242. if brush:
  243. pdc.SetBrush(brush)
  244. if robj.type & (
  245. TYPE_POINT | TYPE_CENTROIDIN | TYPE_CENTROIDOUT | TYPE_CENTROIDDUP |
  246. TYPE_NODEONE | TYPE_NODETWO | TYPE_VERTEX): # -> point
  247. if dcId > 0:
  248. if robj.type == TYPE_VERTEX:
  249. dcId = 3 # first vertex
  250. elif robj.type & (TYPE_NODEONE | TYPE_NODETWO):
  251. if self.firstNode:
  252. dcId = 1
  253. self.firstNode = False
  254. else:
  255. dcId = self.lastNodeId
  256. for i in range(robj.npoints):
  257. p = robj.point[i]
  258. if dcId > 0:
  259. pdc.SetId(dcId)
  260. dcId += 2
  261. self._drawCross(pdc, p)
  262. else:
  263. if dcId > 0 and self._drawSegments:
  264. self.fisrtNode = True
  265. self.lastNodeId = robj.npoints * 2 - 1
  266. dcId = 2 # first segment
  267. i = 0
  268. while i < robj.npoints - 1:
  269. point_beg = wx.Point(robj.point[i].x, robj.point[i].y)
  270. point_end = wx.Point(
  271. robj.point[
  272. i + 1].x,
  273. robj.point[
  274. i + 1].y)
  275. # set unique id & set bbox for each segment
  276. pdc.SetId(dcId)
  277. pdc.SetPen(pen)
  278. pdc.SetIdBounds(
  279. dcId - 1,
  280. Rect(
  281. point_beg.x,
  282. point_beg.y,
  283. 0,
  284. 0))
  285. pdc.SetIdBounds(dcId, wx.RectPP(point_beg, point_end))
  286. pdc.DrawLine(point_beg.x, point_beg.y,
  287. point_end.x, point_end.y)
  288. i += 1
  289. dcId += 2
  290. pdc.SetIdBounds(
  291. dcId - 1,
  292. Rect(
  293. robj.point[
  294. robj.npoints - 1].x,
  295. robj.point[
  296. robj.npoints - 1].y,
  297. 0,
  298. 0))
  299. else:
  300. points = list()
  301. for i in range(robj.npoints):
  302. p = robj.point[i]
  303. points.append(wx.Point(p.x, p.y))
  304. if robj.type == TYPE_AREA:
  305. pdc.DrawPolygon(points)
  306. else:
  307. pdc.DrawLines(points)
  308. def _definePen(self, rtype):
  309. """Define pen/brush based on rendered object)
  310. Updates also self.topology dict
  311. :return: pen, brush
  312. """
  313. if rtype == TYPE_POINT:
  314. key = 'point'
  315. elif rtype == TYPE_LINE:
  316. key = 'line'
  317. elif rtype == TYPE_BOUNDARYNO:
  318. key = 'boundaryNo'
  319. elif rtype == TYPE_BOUNDARYTWO:
  320. key = 'boundaryTwo'
  321. elif rtype == TYPE_BOUNDARYONE:
  322. key = 'boundaryOne'
  323. elif rtype == TYPE_CENTROIDIN:
  324. key = 'centroidIn'
  325. elif rtype == TYPE_CENTROIDOUT:
  326. key = 'centroidOut'
  327. elif rtype == TYPE_CENTROIDDUP:
  328. key = 'centroidDup'
  329. elif rtype == TYPE_NODEONE:
  330. key = 'nodeOne'
  331. elif rtype == TYPE_NODETWO:
  332. key = 'nodeTwo'
  333. elif rtype == TYPE_VERTEX:
  334. key = 'vertex'
  335. elif rtype == TYPE_AREA:
  336. key = 'area'
  337. elif rtype == TYPE_ISLE:
  338. key = 'isle'
  339. elif rtype == TYPE_DIRECTION:
  340. key = 'direction'
  341. if key not in ('direction', 'area', 'isle'):
  342. self.topology[key] += 1
  343. if key in ('area', 'isle'):
  344. pen = wx.TRANSPARENT_PEN
  345. if key == 'area':
  346. brush = wx.Brush(self.settings[key]['color'], wx.SOLID)
  347. else:
  348. brush = wx.TRANSPARENT_BRUSH
  349. else:
  350. pen = wx.Pen(
  351. self.settings[key]['color'],
  352. self.settings['lineWidth'],
  353. wx.SOLID)
  354. brush = None
  355. return pen, brush
  356. def _getDrawFlag(self):
  357. """Get draw flag from the settings
  358. See vedit.h for list of draw flags.
  359. :return: draw flag (int)
  360. """
  361. ret = 0
  362. if self.settings['point']['enabled']:
  363. ret |= DRAW_POINT
  364. if self.settings['line']['enabled']:
  365. ret |= DRAW_LINE
  366. if self.settings['boundaryNo']['enabled']:
  367. ret |= DRAW_BOUNDARYNO
  368. if self.settings['boundaryTwo']['enabled']:
  369. ret |= DRAW_BOUNDARYTWO
  370. if self.settings['boundaryOne']['enabled']:
  371. ret |= DRAW_BOUNDARYONE
  372. if self.settings['centroidIn']['enabled']:
  373. ret |= DRAW_CENTROIDIN
  374. if self.settings['centroidOut']['enabled']:
  375. ret |= DRAW_CENTROIDOUT
  376. if self.settings['centroidDup']['enabled']:
  377. ret |= DRAW_CENTROIDDUP
  378. if self.settings['nodeOne']['enabled']:
  379. ret |= DRAW_NODEONE
  380. if self.settings['nodeTwo']['enabled']:
  381. ret |= DRAW_NODETWO
  382. if self.settings['vertex']['enabled']:
  383. ret |= DRAW_VERTEX
  384. if self.settings['area']['enabled']:
  385. ret |= DRAW_AREA
  386. if self.settings['direction']['enabled']:
  387. ret |= DRAW_DIRECTION
  388. return ret
  389. def _isSelected(self, line, force=False):
  390. """Check if vector object selected?
  391. :param line: feature id
  392. :return: True if vector object is selected
  393. :return: False if vector object is not selected
  394. """
  395. if line in self.selected['ids']:
  396. return True
  397. return False
  398. def _isDuplicated(self, line):
  399. """Check for already marked duplicates
  400. :param line: feature id
  401. :return: True line already marked as duplicated
  402. :return: False not duplicated
  403. """
  404. return line in self.selected['idsDupl']
  405. def _getRegionBox(self):
  406. """Get bound_box() from current region
  407. :return: bound_box
  408. """
  409. box = bound_box()
  410. box.N = self.region['n']
  411. box.S = self.region['s']
  412. box.E = self.region['e']
  413. box.W = self.region['w']
  414. box.T = PORT_DOUBLE_MAX
  415. box.B = -PORT_DOUBLE_MAX
  416. return box
  417. def DrawMap(self, force=False):
  418. """Draw content of the vector map to the device
  419. :param force: force drawing
  420. :type force: bool
  421. :return: number of drawn features
  422. :return: -1 on error
  423. """
  424. Debug.msg(1, "DisplayDriver.DrawMap(): force=%d", force)
  425. if not self.poMapInfo or not self.dc or not self.dcTmp:
  426. return -1
  427. rlist = Vedit_render_map(
  428. self.poMapInfo, byref(self._getRegionBox()),
  429. self._getDrawFlag(),
  430. self.region['center_easting'],
  431. self.region['center_northing'],
  432. self.mapObj.width, self.mapObj.height,
  433. max(self.region['nsres'],
  434. self.region['ewres'])).contents
  435. self._resetTopology()
  436. self.dc.BeginDrawing()
  437. self.dcTmp.BeginDrawing()
  438. # draw objects
  439. for i in range(rlist.nitems):
  440. robj = rlist.item[i].contents
  441. self._drawObject(robj)
  442. self.dc.EndDrawing()
  443. self.dcTmp.EndDrawing()
  444. # reset list of selected features by cat
  445. # list of ids - see IsSelected()
  446. self.selected['field'] = -1
  447. self.selected['cats'] = list()
  448. def _getSelectType(self):
  449. """Get type(s) to be selected
  450. Used by SelectLinesByBox() and SelectLineByPoint()
  451. """
  452. ftype = 0
  453. for feature in (('point', GV_POINT),
  454. ('line', GV_LINE),
  455. ('centroid', GV_CENTROID),
  456. ('boundary', GV_BOUNDARY)):
  457. if UserSettings.Get(group='vdigit', key='selectType',
  458. subkey=[feature[0], 'enabled']):
  459. ftype |= feature[1]
  460. return ftype
  461. def _validLine(self, line):
  462. """Check if feature id is valid
  463. :param line: feature id
  464. :return: True valid feature id
  465. :return: False invalid
  466. """
  467. if line > 0 and line <= Vect_get_num_lines(self.poMapInfo):
  468. return True
  469. return False
  470. def SelectLinesByBox(self, bbox, ltype=None,
  471. drawSeg=False, poMapInfo=None):
  472. """Select vector objects by given bounding box
  473. If line id is already in the list of selected lines, then it will
  474. be excluded from this list.
  475. :param bbox: bounding box definition
  476. :param ltype: feature type or None for default
  477. :param drawSeg: True to draw segments of line
  478. :param poMapInfo: use external Map_info, None for self.poMapInfo
  479. :return: number of selected features
  480. :return: None on error
  481. """
  482. thisMapInfo = poMapInfo is None
  483. if not poMapInfo:
  484. poMapInfo = self.poMapInfo
  485. if not poMapInfo:
  486. return None
  487. if thisMapInfo:
  488. self._drawSegments = drawSeg
  489. self._drawSelected = True
  490. # select by ids
  491. self.selected['cats'] = list()
  492. poList = Vect_new_list()
  493. x1, y1 = bbox[0]
  494. x2, y2 = bbox[1]
  495. poBbox = Vect_new_line_struct()
  496. Vect_append_point(poBbox, x1, y1, 0.0)
  497. Vect_append_point(poBbox, x2, y1, 0.0)
  498. Vect_append_point(poBbox, x2, y2, 0.0)
  499. Vect_append_point(poBbox, x1, y2, 0.0)
  500. Vect_append_point(poBbox, x1, y1, 0.0)
  501. if not ltype:
  502. ltype = self._getSelectType()
  503. Vect_select_lines_by_polygon(poMapInfo, poBbox,
  504. 0, None, # isles
  505. ltype, poList)
  506. flist = poList.contents
  507. nlines = flist.n_values
  508. Debug.msg(1, "DisplayDriver.SelectLinesByBox() num = %d", nlines)
  509. for i in range(nlines):
  510. line = flist.value[i]
  511. if UserSettings.Get(group='vdigit', key='selectInside',
  512. subkey='enabled'):
  513. inside = True
  514. if not self._validLine(line):
  515. return None
  516. Vect_read_line(poMapInfo, self.poPoints, None, line)
  517. points = self.poPoints.contents
  518. for p in range(points.n_points):
  519. if not Vect_point_in_poly(points.x[p], points.y[p],
  520. poBbox):
  521. inside = False
  522. break
  523. if not inside:
  524. continue # skip lines just overlapping bbox
  525. if not self._isSelected(line):
  526. self.selected['ids'].append(line)
  527. else:
  528. self.selected['ids'].remove(line)
  529. Vect_destroy_line_struct(poBbox)
  530. Vect_destroy_list(poList)
  531. return nlines
  532. def SelectAreaByPoint(self, point, poMapInfo=None):
  533. thisMapInfo = poMapInfo is None
  534. if not poMapInfo:
  535. poMapInfo = self.poMapInfo
  536. if not poMapInfo:
  537. return {'area': -1, 'centroid': -1}
  538. if thisMapInfo:
  539. self._drawSelected = True
  540. box = bound_box()
  541. for area in range(1, Vect_get_num_areas(poMapInfo) + 1):
  542. Vect_get_area_box(poMapInfo, area, byref(box))
  543. if Vect_point_in_area(
  544. point[0],
  545. point[1],
  546. poMapInfo, area, byref(box)) == 1:
  547. centroid = Vect_get_area_centroid(poMapInfo, area)
  548. if not self._isSelected(centroid):
  549. self.selected['ids'].append(centroid)
  550. else:
  551. self.selected['ids'].remove(centroid)
  552. return {'area': area, 'centroid': centroid}
  553. return {'area': -1, 'centroid': -1}
  554. def SelectLineByPoint(self, point, ltype=None, poMapInfo=None):
  555. """Select vector feature by given point in given
  556. threshold
  557. Only one vector object can be selected. Bounding boxes of
  558. all segments are stores.
  559. :param point: points coordinates (x, y)
  560. :param ltype: feature type or None for default
  561. :param poMapInfo: use external Map_info, None for self.poMapInfo
  562. :return: dict {'line' : feature id, 'point' : point on line}
  563. """
  564. thisMapInfo = poMapInfo is None
  565. if not poMapInfo:
  566. poMapInfo = self.poMapInfo
  567. if not poMapInfo:
  568. return {'line': -1, 'point': None}
  569. if thisMapInfo:
  570. self._drawSelected = True
  571. # select by ids
  572. self.selected['cats'] = list()
  573. poFound = Vect_new_list()
  574. if ltype is None:
  575. ltype = self._getSelectType()
  576. lineNearest = Vect_find_line_list(
  577. poMapInfo, point[0],
  578. point[1],
  579. 0, ltype, self.GetThreshold(),
  580. self.is3D, None, poFound)
  581. Debug.msg(
  582. 1,
  583. "DisplayDriver.SelectLineByPoint() found = %d",
  584. lineNearest)
  585. if lineNearest > 0:
  586. if not self._isSelected(lineNearest):
  587. self.selected['ids'].append(lineNearest)
  588. else:
  589. self.selected['ids'].remove(lineNearest)
  590. px = c_double()
  591. py = c_double()
  592. pz = c_double()
  593. if not self._validLine(lineNearest):
  594. return {'line': -1, 'point': None}
  595. ftype = Vect_read_line(
  596. poMapInfo,
  597. self.poPoints,
  598. self.poCats,
  599. lineNearest)
  600. Vect_line_distance(self.poPoints, point[0], point[1], 0.0, self.is3D,
  601. byref(px), byref(py), byref(pz),
  602. None, None, None)
  603. # check for duplicates
  604. if self.settings['highlightDupl']['enabled']:
  605. found = poFound.contents
  606. for i in range(found.n_values):
  607. line = found.value[i]
  608. if line != lineNearest:
  609. self.selected['ids'].append(line)
  610. self.GetDuplicates()
  611. for i in range(found.n_values):
  612. line = found.value[i]
  613. if line != lineNearest and not self._isDuplicated(line):
  614. self.selected['ids'].remove(line)
  615. Vect_destroy_list(poFound)
  616. if thisMapInfo:
  617. # drawing segments can be very expensive
  618. # only one features selected
  619. self._drawSegments = True
  620. return {'line': lineNearest,
  621. 'point': (px.value, py.value, pz.value)}
  622. def _listToIList(self, plist):
  623. """Generate from list struct_ilist
  624. """
  625. ilist = Vect_new_list()
  626. for val in plist:
  627. Vect_list_append(ilist, val)
  628. return ilist
  629. def GetSelectedIList(self, ilist=None):
  630. """Get list of selected objects as struct_ilist
  631. Returned IList must be freed by Vect_destroy_list().
  632. :return: struct_ilist
  633. """
  634. if ilist:
  635. return self._listToIList(ilist)
  636. return self._listToIList(self.selected['ids'])
  637. def GetSelected(self, grassId=True):
  638. """Get ids of selected objects
  639. :param grassId: True for feature id, False for PseudoDC id
  640. :return: list of ids of selected vector objects
  641. """
  642. if grassId:
  643. return self.selected['ids']
  644. dc_ids = list()
  645. if not self._drawSegments:
  646. dc_ids.append(1)
  647. elif len(self.selected['ids']) > 0:
  648. # only first selected feature
  649. Vect_read_line(self.poMapInfo, self.poPoints, None,
  650. self.selected['ids'][0])
  651. points = self.poPoints.contents
  652. # node - segment - vertex - segment - node
  653. for i in range(1, 2 * points.n_points):
  654. dc_ids.append(i)
  655. return dc_ids
  656. def SetSelected(self, ids, layer=-1):
  657. """Set selected vector objects
  658. :param list: of ids (None to unselect features)
  659. :param layer: layer number for features selected based on category number
  660. """
  661. if ids:
  662. self._drawSelected = True
  663. else:
  664. self._drawSelected = False
  665. self.selected['field'] = layer
  666. if layer > 0:
  667. self.selected['cats'] = ids
  668. self.selected['ids'] = list()
  669. ### cidx is not up-to-date
  670. # Vect_cidx_find_all(self.poMapInfo, layer, GV_POINTS | GV_LINES, lid, ilist)
  671. nlines = Vect_get_num_lines(self.poMapInfo)
  672. for line in range(1, nlines + 1):
  673. if not Vect_line_alive(self.poMapInfo, line):
  674. continue
  675. ltype = Vect_read_line(self.poMapInfo, None, self.poCats, line)
  676. if not (ltype & (GV_POINTS | GV_LINES)):
  677. continue
  678. found = False
  679. cats = self.poCats.contents
  680. for i in range(0, cats.n_cats):
  681. for cat in self.selected['cats']:
  682. if cats.cat[i] == cat:
  683. found = True
  684. break
  685. if found:
  686. self.selected['ids'].append(line)
  687. else:
  688. self.selected['ids'] = ids
  689. self.selected['cats'] = []
  690. def GetSelectedVertex(self, pos):
  691. """Get PseudoDC vertex id of selected line
  692. Set bounding box for vertices of line.
  693. :param pos: position
  694. :return: id of center, left and right vertex
  695. :return: 0 no line found
  696. :return: -1 on error
  697. """
  698. returnId = list()
  699. # only one object can be selected
  700. if len(self.selected['ids']) != 1 or not self._drawSegments:
  701. return returnId
  702. startId = 1
  703. line = self.selected['ids'][0]
  704. if not self._validLine(line):
  705. return -1
  706. ftype = Vect_read_line(
  707. self.poMapInfo,
  708. self.poPoints,
  709. self.poCats,
  710. line)
  711. minDist = 0.0
  712. Gid = -1
  713. # find the closest vertex (x, y)
  714. DCid = 1
  715. points = self.poPoints.contents
  716. for idx in range(points.n_points):
  717. dist = Vect_points_distance(
  718. pos[0],
  719. pos[1],
  720. 0.0, points.x[idx],
  721. points.y[idx],
  722. points.z[idx],
  723. 0)
  724. if idx == 0:
  725. minDist = dist
  726. Gid = idx
  727. else:
  728. if minDist > dist:
  729. minDist = dist
  730. Gid = idx
  731. vx, vy = self._cell2Pixel(
  732. points.x[idx],
  733. points.y[idx],
  734. points.z[idx])
  735. rect = Rect(vx, vy, 0, 0)
  736. self.dc.SetIdBounds(DCid, rect)
  737. DCid += 2
  738. if minDist > self.GetThreshold():
  739. return returnId
  740. # translate id
  741. DCid = Gid * 2 + 1
  742. # add selected vertex
  743. returnId.append(DCid)
  744. # left vertex
  745. if DCid == startId:
  746. returnId.append(-1)
  747. else:
  748. returnId.append(DCid - 2)
  749. # right vertex
  750. if DCid == (points.n_points - 1) * 2 + startId:
  751. returnId.append(-1)
  752. else:
  753. returnId.append(DCid + 2)
  754. return returnId
  755. def GetRegionSelected(self):
  756. """Get minimal region extent of selected features
  757. :return: n,s,w,e
  758. """
  759. regionBox = bound_box()
  760. lineBox = bound_box()
  761. setRegion = True
  762. nareas = Vect_get_num_areas(self.poMapInfo)
  763. for line in self.selected['ids']:
  764. area = Vect_get_centroid_area(self.poMapInfo, line)
  765. if area > 0 and area <= nareas:
  766. if not Vect_get_area_box(self.poMapInfo, area, byref(lineBox)):
  767. continue
  768. else:
  769. if not Vect_get_line_box(self.poMapInfo, line, byref(lineBox)):
  770. continue
  771. if setRegion:
  772. Vect_box_copy(byref(regionBox), byref(lineBox))
  773. setRegion = False
  774. else:
  775. Vect_box_extend(byref(regionBox), byref(lineBox))
  776. return regionBox.N, regionBox.S, regionBox.W, regionBox.E
  777. def DrawSelected(self, flag):
  778. """Draw selected features
  779. :param flag: True to draw selected features
  780. :type flag: bool
  781. """
  782. self._drawSelected = bool(flag)
  783. def CloseMap(self):
  784. """Close vector map
  785. :return: 0 on success
  786. :return: non-zero on error
  787. """
  788. if not self.poMapInfo:
  789. return 0
  790. if self.poMapInfo.contents.mode == GV_MODE_RW:
  791. # rebuild topology
  792. Vect_build_partial(self.poMapInfo, GV_BUILD_NONE)
  793. Vect_build(self.poMapInfo)
  794. # close map and store topo/cidx
  795. ret = Vect_close(self.poMapInfo)
  796. del self.mapInfo
  797. self.poMapInfo = self.mapInfo = None
  798. return ret
  799. def OpenMap(self, name, mapset, update=True, tmp=False):
  800. """Open vector map by the driver
  801. :param name: name of vector map to be open
  802. :type name: str
  803. :param mapset: name of mapset where the vector map lives
  804. :tryp mapset: str
  805. :param update: True to open vector map in update mode
  806. :type update: bool
  807. :param tmp: True to open temporary vector map
  808. :type tp: bool
  809. :return: map_info
  810. :return: None on error
  811. """
  812. Debug.msg("DisplayDriver.OpenMap(): name=%s mapset=%s updated=%d",
  813. name, mapset, update)
  814. if not self.mapInfo:
  815. self.mapInfo = Map_info()
  816. self.poMapInfo = pointer(self.mapInfo)
  817. # open existing map
  818. if update:
  819. if tmp:
  820. open_fn = Vect_open_tmp_update
  821. else:
  822. open_fn = Vect_open_update
  823. else:
  824. if tmp:
  825. open_fn = Vect_open_tmp_old
  826. else:
  827. open_fn = Vect_open_old
  828. ret = open_fn(self.poMapInfo, name, mapset)
  829. if ret == -1:
  830. # fatal error detected
  831. del self.mapInfo
  832. self.poMapInfo = self.mapInfo = None
  833. elif ret < 2:
  834. # map open at level 1, try to build topology
  835. dlg = wx.MessageDialog(
  836. parent=self.window,
  837. message=_(
  838. "Topology for vector map <%s> is not available. "
  839. "Topology is required by digitizer. Do you want to "
  840. "rebuild topology (takes some time) and open the vector map "
  841. "for editing?") %
  842. name,
  843. caption=_("Topology missing"),
  844. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  845. ret = dlg.ShowModal()
  846. if ret != wx.ID_YES:
  847. del self.mapInfo
  848. self.poMapInfo = self.mapInfo = None
  849. else:
  850. Vect_build(self.poMapInfo)
  851. if update:
  852. # track updated lines at update mode
  853. Vect_set_updated(self.poMapInfo, True)
  854. self.is3D = Vect_is_3d(self.poMapInfo)
  855. return self.poMapInfo
  856. def GetMapBoundingBox(self):
  857. """Get bounding box of (opened) vector map layer
  858. :return: (w,s,b,e,n,t)
  859. """
  860. if not self.poMapInfo:
  861. return None
  862. bbox = bound_box()
  863. Vect_get_map_box(self.poMapInfo, byref(bbox))
  864. return bbox.W, bbox.S, bbox.B, \
  865. bbox.E, bbox.N, bbox.T
  866. def UpdateSettings(self, alpha=255):
  867. """Update display driver settings
  868. .. todo::
  869. map units
  870. :param alpha: color value for aplha channel
  871. """
  872. color = dict()
  873. for key in self.settings.keys():
  874. if key == 'lineWidth':
  875. self.settings[key] = int(
  876. UserSettings.Get(
  877. group='vdigit',
  878. key='lineWidth',
  879. subkey='value'))
  880. continue
  881. color = wx.Colour(UserSettings.Get(group='vdigit', key='symbol',
  882. subkey=[key, 'color'])[0],
  883. UserSettings.Get(group='vdigit', key='symbol',
  884. subkey=[key, 'color'])[1],
  885. UserSettings.Get(group='vdigit', key='symbol',
  886. subkey=[key, 'color'])[2],
  887. alpha)
  888. if key == 'highlight':
  889. self.settings[key] = color
  890. continue
  891. if key == 'highlightDupl':
  892. self.settings[key]['enabled'] = bool(UserSettings.Get(
  893. group='vdigit', key='checkForDupl', subkey='enabled'))
  894. else:
  895. self.settings[key]['enabled'] = bool(UserSettings.Get(
  896. group='vdigit', key='symbol', subkey=[key, 'enabled']))
  897. self.settings[key]['color'] = color
  898. def UpdateRegion(self):
  899. """Update geographical region used by display driver
  900. """
  901. self.region = self.mapObj.GetCurrentRegion()
  902. def GetThreshold(self, type='snapping', value=None, units=None):
  903. """Return threshold value in map units
  904. :param type: snapping mode (node, vertex)
  905. :param value: threshold to be set up
  906. :param units: units (0 for screen pixels, 1 for map units)
  907. :return: threshold value
  908. """
  909. if value is None:
  910. value = UserSettings.Get(group='vdigit', key=type, subkey='value')
  911. if units is None:
  912. units = UserSettings.Get(group='vdigit', key=type, subkey='unit')
  913. if units is None:
  914. # old for backwards comp.
  915. units = UserSettings.Get(group='vdigit', key=type, subkey='units')
  916. units = 0 if units == 'screen pixels' else 1
  917. if value < 0:
  918. value = (self.region['nsres'] + self.region['ewres']) / 2.0
  919. if units == 0:
  920. # pixel -> cell
  921. res = max(self.region['nsres'], self.region['ewres'])
  922. return value * res
  923. return value
  924. def GetDuplicates(self):
  925. """Return ids of (selected) duplicated vector features
  926. """
  927. if not self.poMapInfo:
  928. return
  929. ids = dict()
  930. APoints = Vect_new_line_struct()
  931. BPoints = Vect_new_line_struct()
  932. self.selected['idsDupl'] = list()
  933. for i in range(len(self.selected['ids'])):
  934. line1 = self.selected['ids'][i]
  935. if self._isDuplicated(line1):
  936. continue
  937. Vect_read_line(self.poMapInfo, APoints, None, line1)
  938. for line2 in self.selected['ids']:
  939. if line1 == line2 or self._isDuplicated(line2):
  940. continue
  941. Vect_read_line(self.poMapInfo, BPoints, None, line2)
  942. if Vect_line_check_duplicate(APoints, BPoints, WITHOUT_Z):
  943. if i not in ids:
  944. ids[i] = list()
  945. ids[i].append((line1, self._getCatString(line1)))
  946. self.selected['idsDupl'].append(line1)
  947. ids[i].append((line2, self._getCatString(line2)))
  948. self.selected['idsDupl'].append(line2)
  949. Vect_destroy_line_struct(APoints)
  950. Vect_destroy_line_struct(BPoints)
  951. return ids
  952. def _getCatString(self, line):
  953. Vect_read_line(self.poMapInfo, None, self.poCats, line)
  954. cats = self.poCats.contents
  955. catsDict = dict()
  956. for i in range(cats.n_cats):
  957. layer = cats.field[i]
  958. if layer not in catsDict:
  959. catsDict[layer] = list()
  960. catsDict[layer].append(cats.cat[i])
  961. catsStr = ''
  962. for l, c in six.iteritems(catsDict):
  963. catsStr = '%d: (%s)' % (l, ','.join(map(str, c)))
  964. return catsStr
  965. def UnSelect(self, lines):
  966. """Unselect vector features
  967. :param lines: list of feature id(s)
  968. """
  969. checkForDupl = False
  970. for line in lines:
  971. if self._isSelected(line):
  972. self.selected['ids'].remove(line)
  973. if self.settings['highlightDupl'][
  974. 'enabled'] and self._isDuplicated(line):
  975. checkForDupl = True
  976. if checkForDupl:
  977. self.GetDuplicates()
  978. return len(self.selected['ids'])