4 @brief wxGUI vector digitizer (base class)
6 Code based on wxVdigit C++ component from GRASS 6.4.0
7 (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.
13 (C) 2007-2011 by the GRASS Development Team
15 This program is free software under the GNU General Public License
16 (>=v2). Read the file COPYING that comes with GRASS for details.
18 @author Martin Landa <landa.martin gmail.com>
21 from gcmd
import GError
22 from debug
import Debug
23 from preferences
import globalSettings
as UserSettings
25 from wxvdriver
import DisplayDriver
34 """!Class for managing error messages of vector digitizer
36 @param parent parent window for dialogs
42 """!No map for editing"""
44 message = _(
'Unable to open vector map <%s>.') % name
46 message = _(
'No vector map open for editing.')
47 GError(message +
' ' + _(
'Operation cancelled.'),
52 """!Writing line failed
54 GError(message = _(
'Writing new feature failed. '
55 'Operation cancelled.'),
60 """!Reading line failed
62 GError(message = _(
'Reading feature id %d failed. '
63 'Operation cancelled.') % line,
68 """!No dblink available
70 GError(message = _(
'Database link %d not available. '
71 'Operation cancelled.') % dblink,
76 """!Staring driver failed
78 GError(message = _(
'Unable to start database driver <%s>. '
79 'Operation cancelled.') % driver,
84 """!Opening database failed
86 GError(message = _(
'Unable to open database <%(db)s> by driver <%(driver)s>. '
87 'Operation cancelled.') % {
'db' : database,
'driver' : driver},
94 GError(message = _(
"Unable to execute SQL query '%s'. "
95 "Operation cancelled.") % sql,
102 GError(message = _(
"Feature id %d is marked as dead. "
103 "Operation cancelled.") % line,
108 """!Unknown feature type
110 GError(message = _(
"Unsupported feature type %d. "
111 "Operation cancelled.") % ftype,
117 """!Base class for vector digitizer (ctypes interface)
119 @parem mapwindow reference for map window (BufferedWindow)
128 if not mapwindow.parent.IsStandalone():
129 goutput = mapwindow.parent.GetLayerManager().GetLogWindow()
130 log = goutput.GetLog(err =
True)
131 progress = goutput.GetProgressBar()
136 self.
toolbar = mapwindow.parent.toolbars[
'vdigit']
140 self.
_display = DisplayDriver(device = mapwindow.pdcVector,
141 deviceTmp = mapwindow.pdcTmp,
142 mapObj = mapwindow.Map,
145 gprogress = progress)
168 Debug.msg(1,
"IVDigit.__del__()")
180 """!Close background vector map"""
188 """!Open background vector map
190 @todo support more background maps then only one
192 @param bgmap name of vector map to be opened
194 @return pointer to map_info
195 @return None on error
197 name = create_string_buffer(GNAME_MAX)
198 mapset = create_string_buffer(GMAPSET_MAX)
203 name = str(name.value)
204 mapset = str(mapset.value)
209 self._error.NoMap(bgmap)
216 self._error.NoMap(bgmap)
219 def _getSnapMode(self):
220 """!Get snapping mode
228 threshold = self._display.GetThreshold()
230 if UserSettings.Get(group =
'vdigit', key =
'snapToVertex', subkey =
'enabled'):
237 def _breakLineAtIntersection(self, line, pointsLine, changeset):
238 """!Break given line at intersection
241 \param pointsLine line geometry
244 \return number of modified lines
254 self._error.ReadLine(line)
266 lineBox = bound_box()
275 for i
in range(listLine.contents.n_values):
276 lineBreak = listLine.contents.value[i]
277 if lineBreak == line:
281 if not (ltype & GV_LINES):
290 for i
in range(listBreak.contents.n_values):
296 for i
in range(listBreak.contents.n_values):
312 def _addActionsBefore(self):
313 """!Register action before operation
318 for line
in self._display.selected[
'ids']:
324 def _applyChangeset(self, changeset, undo):
325 """!Apply changeset (undo/redo changeset)
327 @param changeset changeset id
328 @param undo True for undo otherwise redo
330 @return 1 changeset applied
331 @return 0 changeset not applied
334 if changeset < 0
or changeset > len(self.changesets.keys()):
342 for action
in actions:
344 line = action[
'line']
345 if (undo
and add)
or \
346 (
not undo
and not add):
348 Debug.msg(3,
"IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted",
353 Debug.msg(3,
"Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead",
356 offset = action[
'offset']
358 Debug.msg(3,
"Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added",
364 Debug.msg(3,
"Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive",
369 def _addActionsAfter(self, changeset, nlines):
370 """!Register action after operation
372 @param changeset changeset id
373 @param nline number of lines
375 for line
in self._display.selected[
'ids']:
383 def _addActionToChangeset(self, changeset, line, add):
384 """!Add action to changeset
386 @param changeset id of changeset
387 @param line feature id
388 @param add True to add, otherwise delete
402 self.
changesets[changeset].append({
'add' : add,
406 Debug.msg(3,
"IVDigit._addActionToChangeset(): changeset=%d, add=%d, line=%d, offset=%d",
407 changeset, add, line, offset)
409 def _removeActionFromChangeset(self, changeset, line, add):
410 """!Remove action from changeset
412 @param changeset changeset id
414 @param add True for add, False for delete
416 @return number of actions in changeset
419 if changeset
not in self.changesets.keys():
424 if action[
'add'] == add
and action[
'line'] == line:
432 @param ftype feature type (point, line, centroid, boundary)
433 @param points tuple of points ((x, y), (x, y), ...)
435 @return tuple (number of added features, feature ids)
437 if UserSettings.Get(group =
'vdigit', key =
"categoryMode", subkey =
'selection') == 2:
441 layer = UserSettings.Get(group =
'vdigit', key =
"layer", subkey =
'value')
446 elif ftype ==
'line':
448 elif ftype ==
'centroid':
450 elif ftype ==
'boundary':
452 elif ftype ==
'area':
456 message = _(
"Unknown feature type '%s'") % ftype)
459 if vtype & GV_LINES
and len(points) < 2:
461 message = _(
"Not enough points for line"))
464 self.toolbar.EnableUndo()
470 """!Delete selected features
472 @return number of deleted features
474 deleteRec = UserSettings.Get(group =
'vdigit', key =
'delRecord', subkey =
'enabled')
485 for i
in self._display.selected[
'ids']:
488 self._error.ReadLine(i)
491 cats = poCats.contents
492 for j
in range(cats.n_cats):
500 poList = self._display.GetSelectedIList()
503 self._display.selected[
'ids'] = list()
505 if nlines > 0
and deleteRec:
507 poHandle = pointer(handle)
509 poStmt = pointer(stmt)
511 for dblink
in range(n_dblinks):
514 self._error.DbLink(dblink)
520 self._error.Driver(Fi.driver)
526 self._error.Database(Fi.driver, Fi.database)
532 catsDel = poCatsDel.contents
533 for c
in range(catsDel.n_cats):
534 if catsDel.field[c] == Fi.number:
555 self.toolbar.EnableUndo()
560 """!Move selected features
562 @param move direction (x, y)
567 thresh = self._display.GetThreshold()
575 poList = self._display.GetSelectedIList()
587 if nlines > 0
and self.
_settings[
'breakLines']:
588 for i
in range(1, nlines):
592 self.toolbar.EnableUndo()
597 """!Move selected vertex of the line
599 @param point location point
600 @param move x,y direction
602 @return id of new feature
603 @return 0 vertex not moved (not found, line is not selected)
609 if len(self._display.selected[
'ids']) != 1:
620 poList = self._display.GetSelectedIList()
623 self._display.GetThreshold(type =
'selectThresh'),
624 self._display.GetThreshold(),
625 move[0], move[1], 0.0,
634 if moved > 0
and self.
_settings[
'breakLines']:
639 self.toolbar.EnableUndo()
644 """!Add new vertex to the selected line/boundary on position 'coords'
646 @param coords coordinates to add vertex
648 @return id of new feature
649 @return 0 nothing changed
650 @return -1 on failure
655 self.toolbar.EnableUndo()
660 """!Remove vertex from the selected line/boundary on position 'coords'
662 @param coords coordinates to remove vertex
664 @return id of new feature
665 @return 0 nothing changed
666 @return -1 on failure
671 self.toolbar.EnableUndo()
677 """!Split/break selected line/boundary on given position
679 @param point point where to split line
681 @return 1 line modified
682 @return 0 nothing changed
685 thresh = self._display.GetThreshold(
'selectThresh')
689 poList = self._display.GetSelectedIList()
703 self.toolbar.EnableUndo()
710 """!Edit existing line/boundary
712 @param line feature id to be modified
713 @param coords list of coordinates of modified line
715 @return feature id of new line
726 self._error.DeadLine(line)
732 self._error.ReadLine(line)
743 modeSnap =
not (snap == SNAP)
746 -1, self.
poPoints, self._display.GetThreshold(), modeSnap)
755 self.toolbar.EnableUndo()
759 if newline > 0
and self.
_settings[
'breakLines']:
765 """!Flip selected lines/boundaries
767 @return number of modified lines
778 poList = self._display.GetSelectedIList()
784 self.toolbar.EnableUndo()
791 """!Merge selected lines/boundaries
793 @return number of modified lines
803 poList = self._display.GetSelectedIList()
809 self.toolbar.EnableUndo()
816 """!Break selected lines/boundaries
818 @return number of modified lines
828 poList = self._display.GetSelectedIList()
835 self.toolbar.EnableUndo()
842 """!Snap selected lines/boundaries
854 poList = self._display.GetSelectedIList()
856 self._display.GetThreshold(),
None)
861 self.toolbar.EnableUndo()
866 """!Connect selected lines/boundaries
868 @return 1 lines connected
869 @return 0 lines not connected
880 poList = self._display.GetSelectedIList()
882 self._display.GetThreshold())
887 self.toolbar.EnableUndo()
894 """!Copy features from (background) vector map
896 @param ids list of line ids to be copied
898 @return number of copied features
906 poList = self._display.GetSelectedIList(ids)
915 self.toolbar.EnableUndo()
920 for i
in range(1, ret):
925 def CopyCats(self, fromId, toId, copyAttrb = False):
926 """!Copy given categories to objects with id listed in ids
928 @param cats ids of 'from' feature
929 @param ids ids of 'to' feature(s)
931 @return number of modified features
934 if len(fromId) < 1
or len(toId) < 1:
947 self._error.ReadLine(fline)
956 self._error.ReadLine(fline)
959 catsFrom = poCatsFrom.contents
960 for i
in range(catsFrom.n_cats):
963 cat = catsFrom.cat[i]
966 cat = self.
cats[catsFrom.field[i]] + 1
967 self.
cats[catsFrom.field[i]] = cat
970 self._error.DbLink(i)
976 self._error.Driver(fi.driver)
984 self._error.Database(fi.driver, fi.database)
990 "SELECT * FROM %s WHERE %s=%d" % (fi.table, fi.key,
995 DB_SEQUENTIAL) != DB_OK:
1002 sql =
"INSERT INTO %s VALUES (" % fi.table
1006 if db_fetch(byref(cursor), DB_NEXT, byref(more)) != DB_OK:
1012 value_string = dbString()
1013 for col
in range(ncols):
1028 if ctype != DB_C_TYPE_STRING:
1046 self._error.WriteLine()
1054 self.toolbar.EnableUndo()
1058 def _selectLinesByQueryThresh(self):
1059 """!Generic method used for SelectLinesByQuery() -- to get
1062 if UserSettings.Get(group =
'vdigit', key =
'query', subkey =
'selection') == 0:
1063 thresh = UserSettings.Get(group =
'vdigit', key =
'queryLength', subkey =
'thresh')
1064 if UserSettings.Get(group =
'vdigit', key =
"queryLength", subkey =
'than-selection') == 0:
1065 thresh = -1 * thresh
1067 thresh = UserSettings.Get(group =
'vdigit', key =
'queryDangle', subkey =
'thresh')
1068 if UserSettings.Get(group =
'vdigit', key =
"queryDangle", subkey =
'than-selection') == 0:
1069 thresh = -1 * thresh
1074 """!Select features by query
1078 @param bbox bounding box definition
1085 query = QUERY_UNKNOWN
1086 if UserSettings.Get(group =
'vdigit', key =
'query', subkey =
'selection') == 0:
1087 query = QUERY_LENGTH
1089 query = QUERY_DANGLE
1091 ftype = GV_POINTS | GV_LINES
1096 coList = poList.contents
1097 if UserSettings.Get(group =
'vdigit', key =
'query', subkey =
'box'):
1112 if coList.n_values == 0:
1116 ftype, layer, thresh, query,
1119 for i
in range(coList.n_values):
1120 ids.append(int(coList.value[i]))
1122 Debug.msg(3,
"IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values)
1128 """!Check if open vector map is 3D
1138 @param line feature id
1151 self._error.ReadLine(line)
1155 if ltype & GV_LINES:
1163 @param centroid centroid id
1173 self._error.ReadLine(line)
1176 if ltype != GV_CENTROID:
1190 """!Get area perimeter
1192 @param centroid centroid id
1202 self._error.ReadLine(line)
1205 if ltype != GV_CENTROID:
1220 """!Set categories for given line and layer
1222 @param line feature id
1223 @param layer layer number (-1 for first selected line)
1224 @param cats list of categories
1225 @param add if True to add, otherwise do delete categories
1227 @return new feature id (feature need to be rewritten)
1233 if line < 1
and len(self._display.selected[
'ids']) < 1:
1239 line = self._display.selected[
'ids'][0]
1246 self._error.ReadLine(line)
1262 self.toolbar.EnableUndo()
1266 self._display.selected[
'ids'][0] = newline
1271 """!Feature type conversion for selected objects.
1273 Supported conversions:
1274 - point <-> centroid
1277 @return number of modified features
1288 poList = self._display.GetSelectedIList()
1294 self.toolbar.EnableUndo()
1303 @param level levels to undo (0 to revert all)
1305 @return id of current changeset
1307 changesetLast = len(self.changesets.keys()) - 1
1309 if changesetLast < 0:
1310 return changesetLast
1320 level = -1 * changesetLast + 1
1322 Debug.msg(2,
"Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d",
1327 return changesetCurrent;
1338 Debug.msg(2,
"Digit.Undo(): changeset_current=%d, changeset_last=%d, changeset_end=%d",
1345 self.mapWindow.UpdateMap(render =
False)
1348 self.toolbar.EnableUndo(
False)
1353 @param pos1 reference line (start point)
1354 @param pos1 reference line (end point)
1355 @param start starting value
1356 @param step step value
1358 @return number of modified lines
1369 poList = self._display.GetSelectedIList()
1371 pos1[0], pos1[1], pos2[0], pos2[1],
1377 self.toolbar.EnableUndo()
1384 """!Get display driver instance"""
1388 """!Open vector map for editing
1390 @param map name of vector map to be set up
1392 Debug.msg (3,
"AbstractDigit.SetMapName map=%s" % name)
1394 name, mapset = name.split(
'@')
1396 self.
poMapInfo = self._display.OpenMap(str(name), str(mapset),
True)
1404 """!Close currently open vector map
1409 self._display.CloseMap()
1412 """!Initialize categories information
1414 @return 0 on success
1422 for i
in range(ndblinks):
1425 self.
cats[fi.number] =
None
1429 Debug.msg(2,
"wxDigit.InitCats(): nfields=%d", nfields)
1431 for i
in range(nfields):
1436 for j
in range(ncats):
1441 byref(cat), byref(type), byref(id))
1442 if field
in self.
cats:
1443 if cat > self.
cats[field]:
1444 self.
cats[field] = cat.value
1446 self.
cats[field] = cat.value
1447 Debug.msg(3,
"wxDigit.InitCats(): layer=%d, cat=%d", field, self.
cats[field])
1450 for field, cat
in self.cats.iteritems():
1452 self.
cats[field] = 0
1453 Debug.msg(3,
"wxDigit.InitCats(): layer=%d, cat=%d", field, self.
cats[field])
1455 def _checkMap(self):
1456 """!Check if map is open
1464 def _addFeature(self, ftype, coords, layer, cat, snap, threshold):
1465 """!Add new feature(s) to the vector map
1467 @param ftype feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...)
1468 @coords tuple of coordinates ((x, y), (x, y), ...)
1469 @param layer layer number (-1 for no cat)
1470 @param cat category number
1471 @param snap snap to node/vertex
1472 @param threshold threshold for snapping
1474 @return tuple (number of added features, list of fids)
1475 @return number of features -1 on error
1483 Debug.msg(2,
"IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d",
1484 len(coords), layer, cat, snap)
1486 if not (ftype & (GV_POINTS | GV_LINES | GV_AREA)):
1487 self._error.FeatureType(ftype)
1492 if layer > 0
and ftype != GV_AREA:
1494 self.
cats[layer] =
max(cat, self.cats.get(layer, 1))
1501 if ftype & (GV_BOUNDARY | GV_AREA):
1503 points = self.poPoints.contents
1504 last = points.n_points - 1
1506 points.x[last], points.x[last], points.z[last],
1508 points.x[last] = points.x[0]
1509 points.y[last] = points.y[0]
1510 points.z[last] = points.z[0]
1514 modeSnap =
not (snap == SNAP)
1516 -1, self.
poPoints, threshold, modeSnap)
1518 if ftype == GV_AREA:
1524 self._error.WriteLine()
1527 fids.append(newline)
1537 byref(cleft), byref(cright))
1539 right = cright.value
1541 Debug.msg(3,
"IVDigit._addFeature(): area - left=%d right=%d",
1545 if layer > 0
and (left > 0
or right > 0):
1547 self.
cats[layer] =
max(cat, self.cats.get(layer, 0))
1561 self._error.WriteLine()
1562 return (len(fids), fids)
1564 fids.append(newline)
1576 self._error.WriteLine()
1577 return (len(fids, fids))
1579 fids.append(newline)
1591 return (len(fids), fids)
1593 def _ModifyLineVertex(self, coords, add = True):
1594 """!Add or remove vertex
1596 Shape of line/boundary is not changed when adding new vertex.
1598 @param coords coordinates of point
1599 @param add True to add, False to remove
1601 @return id id of the new feature
1602 @return 0 nothing changed
1608 selected = self._display.selected
1609 if len(selected[
'ids']) != 1:
1612 poList = self._display.GetSelectedIList()
1617 thresh = self._display.GetThreshold(type =
'selectThresh')
1634 if not add
and ret > 0
and self.
_settings[
'breakLines']:
1641 """!Get list of layer/category(ies) for selected feature.
1643 @param line feature id (-1 for first selected feature)
1645 @return list of layer/cats
1651 if line == -1
and len(self._display.selected[
'ids']) < 1:
1655 line = self._display.selected[
'ids'][0]
1658 self._error.DeadLine(line)
1662 self._error.ReadLine(line)
1665 cats = self.poCats.contents
1666 for i
in range(cats.n_cats):
1667 field = cats.field[i]
1668 if field
not in ret:
1670 ret[field].append(cats.cat[i])
1675 """!Get list of layers
1677 Requires self.InitCats() to be called.
1679 @return list of layers
1681 return self.cats.keys()
1684 """!Update digit (and display) settings
1686 self._display.UpdateSettings()
1688 self.
_settings[
'breakLines'] = bool(UserSettings.Get(group =
'vdigit', key =
"breakLines",
1689 subkey =
'enabled'))
1692 """!Update self.cats based on settings"""
1693 sel = UserSettings.Get(group =
'vdigit', key =
'categoryMode', subkey =
'selection')
1698 cat = UserSettings.Get(group =
'vdigit', key =
'category', subkey =
'value')
1701 layer = UserSettings.Get(group =
'vdigit', key =
'layer', subkey =
'value')
1702 self.
cats[layer] = cat
1706 def _setCategoryNextToUse(self):
1707 """!Find maximum category number for the given layer and
1710 @return category to be used
1713 layer = UserSettings.Get(group =
'vdigit', key =
'layer', subkey =
'value')
1714 cat = self.cats.get(layer, 0) + 1
1715 UserSettings.Set(group =
'vdigit', key =
'category', subkey =
'value',
1717 Debug.msg(1,
"IVDigit._setCategoryNextToUse(): cat=%d", cat)
1722 """!Select features from background map
1724 @param bbox bounding box definition
1726 @return list of selected feature ids
1729 if self._display.SelectLinesByBox(bbox, poMapInfo = self.
poBgMapInfo) < 1:
1730 self._display.SelectLineByPoint(bbox[0], poMapInfo = self.
poBgMapInfo)[
'line']
1732 return self._display.selected[
'ids']
1735 """!Get undo level (number of active changesets)
1737 Note: Changesets starts wiht 0