wxdisplay.py 37 KB

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