wxdisplay.py 38 KB

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