wxdisplay.py 37 KB

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