wxdisplay.py 37 KB

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