wxdisplay.py 36 KB

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