wxdisplay.py 37 KB

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