wxdisplay.py 37 KB

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