""" @package vdigit.wxdigit @brief wxGUI vector digitizer (base class) Code based on wxVdigit C++ component from GRASS 6.4.0 (gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01. List of classes: - wxdigit::VDigitError - wxdigit::IVDigit .. todo:: Read large amounts of data from Vlib into arrays, which could then be processed using NumPy and rendered using glDrawArrays or glDrawElements, so no per-line/per-vertex processing in Python. Bulk data processing with NumPy is much faster than iterating in Python (and NumPy would be an excellent candidate for acceleration via e.g. OpenCL or CUDA; I'm surprised it hasn't happened already). (C) 2007-2016 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. @author Martin Landa """ from __future__ import print_function import six import grass.script.core as grass from grass.pydispatch.signal import Signal from core.gcmd import GError from core.debug import Debug from core.settings import UserSettings from vdigit.wxdisplay import DisplayDriver, GetLastError try: WindowsError except NameError: WindowsError = OSError try: from grass.lib.gis import * from grass.lib.vector import * from grass.lib.vedit import * from grass.lib.dbmi import * except (ImportError, WindowsError, TypeError) as e: print("wxdigit.py: {}".format(e), file=sys.stderr) class VDigitError: def __init__(self, parent): """Class for managing error messages of vector digitizer :param parent: parent window for dialogs """ self.parent = parent self.caption = _("Digitization Error") def NoMap(self, name=None): """No map for editing""" if name: message = _("Unable to open vector map <%s>.") % name else: message = _("No vector map open for editing.") GError( message + " " + _("Operation canceled."), parent=self.parent, caption=self.caption, ) def WriteLine(self): """Writing line failed""" GError( message=_( "Writing new feature failed. " "Operation canceled.\n\n" "Reason: %s" ) % GetLastError(), parent=self.parent, caption=self.caption, ) def ReadLine(self, line): """Reading line failed""" GError( message=_("Reading feature id %d failed. " "Operation canceled.") % line, parent=self.parent, caption=self.caption, ) def DbLink(self, dblink): """No dblink available""" GError( message=_("Database link %d not available. " "Operation canceled.") % dblink, parent=self.parent, caption=self.caption, ) def Driver(self, driver): """Staring driver failed""" GError( message=_("Unable to start database driver <%s>. " "Operation canceled.") % driver, parent=self.parent, caption=self.caption, ) def Database(self, driver, database): """Opening database failed""" GError( message=_( "Unable to open database <%(db)s> by driver <%(driver)s>. " "Operation canceled." ) % {"db": database, "driver": driver}, parent=self.parent, caption=self.caption, ) def DbExecute(self, sql): """Sql query failed""" GError( message=_("Unable to execute SQL query '%s'. " "Operation canceled.") % sql, parent=self.parent, caption=self.caption, ) def DeadLine(self, line): """Dead line""" GError( message=_("Feature id %d is marked as dead. " "Operation canceled.") % line, parent=self.parent, caption=self.caption, ) def FeatureType(self, ftype): """Unknown feature type""" GError( message=_("Unsupported feature type %d. " "Operation canceled.") % ftype, parent=self.parent, caption=self.caption, ) class IVDigit: def __init__(self, giface, mapwindow, driver=DisplayDriver): """Base class for vector digitizer (ctypes interface) :param mapwindow: reference to a map window """ self.poMapInfo = None # pointer to Map_info self.mapWindow = mapwindow self._giface = giface # background map self.bgMapInfo = Map_info() self.poBgMapInfo = self.popoBgMapInfo = None try: progress = self._giface.GetProgress() except NotImplementedError: progress = None log = self._giface.GetLog(err=True) self.toolbar = mapwindow.parent.toolbars["vdigit"] self._error = VDigitError(parent=self.mapWindow) self._display = driver( device=mapwindow.pdcVector, deviceTmp=mapwindow.pdcTmp, mapObj=mapwindow.Map, window=mapwindow, glog=log, gprogress=progress, ) # GRASS lib self.poPoints = Vect_new_line_struct() self.poCats = Vect_new_cats_struct() # self.SetCategory() # layer / max category self.cats = dict() self._settings = dict() self.UpdateSettings() # -> self._settings # undo/redo self.changesets = list() self.changesetCurrent = -1 # first changeset to apply if self.poMapInfo: self.InitCats() self.emit_signals = False # signals which describes features changes during digitization, # activate them using EmitSignals method # currently implemented for functionality used by wx.iclass (for # scatter plot) # signals parameter description: # old_bboxs - list of bboxes of boundary features, which covers changed areas # it is bbox of old state (before edit) # old_areas_cats - list of area categories of boundary features of old state (before edit) # same position in both lists corresponds to same feature # new_bboxs = list of bboxes of created features / after edit # new_areas_cats list of areas cats of created features / after edit # same position in both lists corresponds to same features # for description of items in bbox and area_cats lists see return value # of _getaAreaBboxCats # TODO currently it is not possible to identify corresponded features # in old and new lists (requires changed to vector updated format) # TODO return feature type # TODO handle errors? self.featureAdded = Signal("IVDigit.featureAdded") self.areasDeleted = Signal("IVDigit.areasDeleted") self.vertexMoved = Signal("IVDigit.vertexMoved") self.vertexAdded = Signal("IVDigit.vertexAdded") self.vertexRemoved = Signal("IVDigit.vertexRemoved") self.featuresDeleted = Signal("IVDigit.featuresDeleted") self.featuresMoved = Signal("IVDigit.featuresMoved") self.lineEdited = Signal("IVDigit.lineEdited") def __del__(self): Debug.msg(1, "IVDigit.__del__()") Vect_destroy_line_struct(self.poPoints) self.poPoints = None Vect_destroy_cats_struct(self.poCats) self.poCats = None if self.poBgMapInfo: Vect_close(self.poBgMapInfo) self.poBgMapInfo = self.popoBgMapInfo = None del self.bgMapInfo def EmitSignals(self, emit): """Activate/deactivate signals which describes features changes during digitization.""" self.emit_signals = emit def CloseBackgroundMap(self): """Close background vector map""" if not self.poBgMapInfo: return Vect_close(self.poBgMapInfo) self.poBgMapInfo = self.popoBgMapInfo = None def OpenBackgroundMap(self, bgmap): """Open background vector map .. todo:: support more background maps then only one :param bgmap: name of vector map to be opened :type bgmap: str :return: pointer to map_info :return: None on error """ name = create_string_buffer(GNAME_MAX) mapset = create_string_buffer(GMAPSET_MAX) if not G_name_is_fully_qualified(bgmap, name, mapset): name = bgmap mapset = grass.decode(G_find_vector2(bgmap, "")) else: name = grass.decode(name.value) mapset = grass.decode(mapset.value) if name == Vect_get_name(self.poMapInfo) and mapset == Vect_get_mapset( self.poMapInfo ): self.poBgMapInfo = self.popoBgMapInfo = None self._error.NoMap(bgmap) return self.poBgMapInfo = pointer(self.bgMapInfo) self.popoBgMapInfo = pointer(self.poBgMapInfo) if Vect_open_old(self.poBgMapInfo, name, mapset) == -1: self.poBgMapInfo = self.popoBgMapInfo = None self._error.NoMap(bgmap) return def _getSnapMode(self): """Get snapping mode - snap to vertex - snap to nodes - no snapping :return: snap mode """ threshold = self._display.GetThreshold() if threshold > 0.0: if UserSettings.Get(group="vdigit", key="snapToVertex", subkey="enabled"): return SNAPVERTEX else: return SNAP else: return NO_SNAP def _getNewFeaturesLayer(self): """Returns layer of new feature (from settings)""" if ( UserSettings.Get(group="vdigit", key="categoryMode", subkey="selection") == 2 ): layer = -1 # -> no category else: layer = UserSettings.Get(group="vdigit", key="layer", subkey="value") return layer def _getNewFeaturesCat(self): """Returns category of new feature (from settings)""" if ( UserSettings.Get(group="vdigit", key="categoryMode", subkey="selection") == 2 ): cat = -1 else: cat = self.SetCategory() return cat def _breakLineAtIntersection(self, line, pointsLine): """Break given line at intersection :param line: line id :param pointsLine: line geometry :return: number of modified lines """ if not self._checkMap(): return -1 if not Vect_line_alive(self.poMapInfo, line): return 0 if not pointsLine: if Vect_read_line(self.poMapInfo, self.poPoints, None, line) < 0: self._error.ReadLine(line) return -1 points = self.poPoints else: points = pointsLine listLine = Vect_new_boxlist(0) listRef = Vect_new_list() listBreak = Vect_new_list() pointsCheck = Vect_new_line_struct() lineBox = bound_box() # find all relevant lines Vect_get_line_box(self.poMapInfo, line, byref(lineBox)) Vect_select_lines_by_box(self.poMapInfo, byref(lineBox), GV_LINES, listLine) # check for intersection Vect_list_append(listBreak, line) Vect_list_append(listRef, line) for i in range(listLine.contents.n_values): lineBreak = listLine.contents.id[i] if lineBreak == line: continue ltype = Vect_read_line(self.poMapInfo, pointsCheck, None, lineBreak) if not (ltype & GV_LINES): continue if Vect_line_check_intersection(self.poPoints, pointsCheck, WITHOUT_Z): Vect_list_append(listBreak, lineBreak) ret = Vect_break_lines_list(self.poMapInfo, listBreak, listRef, GV_LINES, None) Vect_destroy_line_struct(pointsCheck) Vect_destroy_boxlist(listLine) Vect_destroy_list(listBreak) Vect_destroy_list(listRef) return ret def _addChangeset(self): # disable redo changesetLast = len(self.changesets) - 1 if self.changesetCurrent < changesetLast and len(self.changesets) > 0: del self.changesets[self.changesetCurrent + 1 : changesetLast + 1] self.toolbar.EnableRedo(False) data = list() for i in range(Vect_get_num_updated_lines(self.poMapInfo) - 1, -1, -1): line = Vect_get_updated_line(self.poMapInfo, i) offset = Vect_get_updated_line_offset(self.poMapInfo, i) data.append({"line": line, "offset": offset}) self.changesetCurrent += 1 self.changesets.insert(self.changesetCurrent, data) Vect_reset_updated(self.poMapInfo) def _applyChangeset(self, changeset, undo): """Apply changeset (undo/redo changeset) :param changeset: changeset id :param undo: True for undo otherwise redo :type undo: bool :return: 1 changeset applied :return: 0 changeset not applied :return: -1 on error """ if changeset < 0 or changeset >= len(self.changesets): return -1 ret = 0 actions = self.changesets[changeset] if undo: firstaction = 0 lastaction = len(actions) step = 1 else: firstaction = len(actions) - 1 lastaction = -1 step = -1 for i in range(firstaction, lastaction, step): action = actions[i] line = action["line"] if action["offset"] > 0: # feature previously added -> to be deleted if Vect_line_alive(self.poMapInfo, line): Debug.msg( 3, "IVDigit._applyChangeset(): changeset=%d, action=add, line=%d -> deleted", changeset, line, ) Vect_delete_line(self.poMapInfo, line) ret = 1 else: Debug.msg( 3, "Digit.ApplyChangeset(): changeset=%d, action=add, line=%d dead", changeset, line, ) else: # feature previously deleted -> to be added offset = abs(action["offset"]) if not Vect_line_alive(self.poMapInfo, line): Debug.msg( 3, "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d -> added", changeset, line, ) if Vect_restore_line(self.poMapInfo, offset, line) < 0: return -1 ret = 1 else: Debug.msg( 3, "Digit.ApplyChangeset(): changeset=%d, action=delete, line=%d alive", changeset, line, ) action["offset"] *= -1 Vect_reset_updated(self.poMapInfo) return ret def AddFeature(self, ftype, points): """Add new feature :param ftype: feature type (point, line, centroid, boundary) :param points: tuple of points ((x, y), (x, y), ...) :return: tuple (number of added features, feature ids) """ layer = self._getNewFeaturesLayer() cat = self._getNewFeaturesCat() if ftype == "point": vtype = GV_POINT elif ftype == "line": vtype = GV_LINE elif ftype == "centroid": vtype = GV_CENTROID elif ftype == "boundary": vtype = GV_BOUNDARY elif ftype == "area": vtype = GV_AREA else: GError( parent=self.mapWindow, message=_("Unknown feature type '%s'") % ftype ) return (-1, None) if vtype & GV_LINES and len(points) < 2: GError(parent=self.mapWindow, message=_("Not enough points for line")) return (-1, None) self.toolbar.EnableUndo() ret = self._addFeature( vtype, points, layer, cat, self._getSnapMode(), self._display.GetThreshold() ) if ret[0] > -1 and self.emit_signals: self.featureAdded.emit( new_bboxs=[self._createBbox(points)], new_areas_cats=[[{layer: [cat]}, None]], ) return ret def DeleteSelectedLines(self): """Delete selected features :return: number of deleted features """ if not self._checkMap(): return -1 # collect categories for deleting if requested deleteRec = UserSettings.Get(group="vdigit", key="delRecord", subkey="enabled") catDict = dict() old_bboxs = [] old_areas_cats = [] if deleteRec: for i in self._display.selected["ids"]: if Vect_read_line(self.poMapInfo, None, self.poCats, i) < 0: self._error.ReadLine(i) if self.emit_signals: ret = self._getLineAreaBboxCats(i) if ret: old_bboxs += ret[0] old_areas_cats += ret[1] # catDict was not used -> put into comment # cats = self.poCats.contents # for j in range(cats.n_cats): # if cats.field[j] not in catDict.keys(): # catDict[cats.field[j]] = list() # catDict[cats.field[j]].append(cats.cat[j]) poList = self._display.GetSelectedIList() nlines = Vedit_delete_lines(self.poMapInfo, poList) Vect_destroy_list(poList) self._display.selected["ids"] = list() if nlines > 0: if deleteRec: self._deleteRecords(catDict) self._addChangeset() self.toolbar.EnableUndo() if self.emit_signals: self.featuresDeleted.emit( old_bboxs=old_bboxs, old_areas_cats=old_areas_cats ) return nlines def _deleteRecords(self, cats): """Delete records from attribute table :param cats: directory field/list of cats """ handle = dbHandle() poHandle = pointer(handle) stmt = dbString() poStmt = pointer(stmt) for dblink in range(Vect_get_num_dblinks(self.poMapInfo)): poFi = Vect_get_dblink(self.poMapInfo, dblink) if poFi is None: self._error.DbLink(dblink) return -1 Fi = poFi.contents if Fi.number not in cats.keys(): continue poDriver = db_start_driver(Fi.driver) if poDriver is None: self._error.Driver(Fi.driver) return -1 db_init_handle(poHandle) db_set_handle(poHandle, Fi.database, None) if db_open_database(poDriver, poHandle) != DB_OK: self._error.Database(Fi.driver, Fi.database) return -1 db_init_string(poStmt) db_set_string(poStmt, "DELETE FROM %s WHERE" % Fi.table) n_cats = 0 for cat in cats[Fi.number]: if n_cats > 0: db_append_string(poStmt, " or") db_append_string(poStmt, " %s = %d" % (Fi.key, cat)) n_cats += 1 if n_cats > 0 and db_execute_immediate(poDriver, poStmt) != DB_OK: self._error.DbExecute(db_get_string(poStmt)) return -1 db_close_database_shutdown_driver(poDriver) def DeleteSelectedAreas(self): """Delete selected areas (centroid+boundaries) :return: number of deleted """ if len(self._display.selected["ids"]) < 1: return 0 poList = self._display.GetSelectedIList() cList = poList.contents nareas = 0 old_bboxs = [] old_areas_cats = [] for i in range(cList.n_values): if Vect_get_line_type(self.poMapInfo, cList.value[i]) != GV_CENTROID: continue if self.emit_signals: area = Vect_get_centroid_area(self.poMapInfo, cList.value[i]) if area > 0: bbox, cats = self._getaAreaBboxCats(area) old_bboxs += bbox old_areas_cats += cats nareas += Vedit_delete_area_centroid(self.poMapInfo, cList.value[i]) if nareas > 0: self._addChangeset() self.toolbar.EnableUndo() if self.emit_signals: self.areasDeleted.emit( old_bboxs=old_bboxs, old_areas_cats=old_areas_cats ) return nareas def _getLineAreaBboxCats(self, ln_id): """Helper function :param ln_id: id of feature :return: None if the feature does not exists :return: list of :func:`_getaAreaBboxCats` """ ltype = Vect_read_line(self.poMapInfo, None, None, ln_id) if ltype == GV_CENTROID: # TODO centroid opttimization, can be edited also its area -> it # will appear two times in new_ lists return self._getCentroidAreaBboxCats(ln_id) else: return [self._getBbox(ln_id)], [self._getLineAreasCategories(ln_id)] def _getCentroidAreaBboxCats(self, centroid): """Helper function :param centroid: id of an centroid :return: None if area does not exists :return: see return of :func:`_getaAreaBboxCats` """ if not Vect_line_alive(self.poMapInfo, centroid): return None area = Vect_get_centroid_area(self.poMapInfo, centroid) if area > 0: return self._getaAreaBboxCats(area) else: return None def _getaAreaBboxCats(self, area): """Helper function :param area: area id :return: list of categories :func:`_getLineAreasCategories` and list of bboxes :func:`_getBbox` of area boundary features """ po_b_list = Vect_new_list() Vect_get_area_boundaries(self.poMapInfo, area, po_b_list) b_list = po_b_list.contents geoms = [] areas_cats = [] if b_list.n_values > 0: for i_line in range(b_list.n_values): line = b_list.value[i_line] geoms.append(self._getBbox(abs(line))) areas_cats.append(self._getLineAreasCategories(abs(line))) Vect_destroy_list(po_b_list) return geoms, areas_cats def _getLineAreasCategories(self, ln_id): """Helper function :param line_id: id of boundary feature :return: categories of areas on the left, right side of the feature :return: format: [[{layer : [cat]}, None]] means: area to the left (list of layers which has cats list as values), area to the right (no area there in this case (None)) :return: [] the feature is not boundary or does not exists """ if not Vect_line_alive(self.poMapInfo, ln_id): return [] ltype = Vect_read_line(self.poMapInfo, None, None, ln_id) if ltype != GV_BOUNDARY: return [] cats = [None, None] left = c_int() right = c_int() if ( Vect_get_line_areas(self.poMapInfo, ln_id, pointer(left), pointer(right)) == 1 ): areas = [left.value, right.value] for i, a in enumerate(areas): if a > 0: centroid = Vect_get_area_centroid(self.poMapInfo, a) if centroid <= 0: continue c = self._getCategories(centroid) if c: cats[i] = c return cats def _getCategories(self, ln_id): """Helper function :param line_id: id of feature :return: list of the feature categories [{layer : cats}, next layer...] :return: None feature does not exist """ if not Vect_line_alive(self.poMapInfo, ln_id): return none poCats = Vect_new_cats_struct() if Vect_read_line(self.poMapInfo, None, poCats, ln_id) < 0: Vect_destroy_cats_struct(poCats) return None cCats = poCats.contents cats = {} for j in range(cCats.n_cats): if cCats.field[j] in cats: cats[cCats.field[j]].append(cCats.cat[j]) else: cats[cCats.field[j]] = [cCats.cat[j]] Vect_destroy_cats_struct(poCats) return cats def _getBbox(self, ln_id): """Helper function :param line_id: id of line feature :return: bbox bounding box of the feature :return: None feature does not exist """ if not Vect_line_alive(self.poMapInfo, ln_id): return None poPoints = Vect_new_line_struct() if Vect_read_line(self.poMapInfo, poPoints, None, ln_id) < 0: Vect_destroy_line_struct(poPoints) return [] geom = self._convertGeom(poPoints) bbox = self._createBbox(geom) Vect_destroy_line_struct(poPoints) return bbox def _createBbox(self, points): """Helper function :param points: list of points [(x, y), ...] to be bbox created for :return: bbox bounding box of points {'maxx':, 'maxy':, 'minx':, 'miny'} """ bbox = {} for pt in points: if "maxy" not in bbox: bbox["maxy"] = pt[1] bbox["miny"] = pt[1] bbox["maxx"] = pt[0] bbox["minx"] = pt[0] continue if bbox["maxy"] < pt[1]: bbox["maxy"] = pt[1] elif bbox["miny"] > pt[1]: bbox["miny"] = pt[1] if bbox["maxx"] < pt[0]: bbox["maxx"] = pt[0] elif bbox["minx"] > pt[0]: bbox["minx"] = pt[0] return bbox def _convertGeom(self, poPoints): """Helper function convert geom from ctypes line_pts to python list :return: coords in python list [(x, y),...] """ Points = poPoints.contents pts_geom = [] for j in range(Points.n_points): pts_geom.append((Points.x[j], Points.y[j])) return pts_geom def MoveSelectedLines(self, move): """Move selected features :param move: direction (x, y) """ if not self._checkMap(): return -1 nsel = len(self._display.selected["ids"]) if nsel < 1: return -1 thresh = self._display.GetThreshold() snap = self._getSnapMode() poList = self._display.GetSelectedIList() if self.emit_signals: old_bboxs = [] old_areas_cats = [] for sel_id in self._display.selected["ids"]: ret = self._getLineAreaBboxCats(sel_id) if ret: old_bboxs += ret[0] old_areas_cats += ret[1] Vect_set_updated(self.poMapInfo, 1) n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo) nlines = Vedit_move_lines( self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None), poList, move[0], move[1], 0, snap, thresh, ) Vect_destroy_list(poList) if nlines > 0 and self.emit_signals: new_bboxs = [] new_areas_cats = [] n_up_lines = Vect_get_num_updated_lines(self.poMapInfo) for i in range(n_up_lines_old, n_up_lines): new_id = Vect_get_updated_line(self.poMapInfo, i) ret = self._getLineAreaBboxCats(new_id) if ret: new_bboxs += ret[0] new_areas_cats += ret[1] if nlines > 0 and self._settings["breakLines"]: for i in range(1, nlines): self._breakLineAtIntersection(nlines + i, None, changeset) if nlines > 0: self._addChangeset() self.toolbar.EnableUndo() if self.emit_signals: self.featuresMoved.emit( new_bboxs=new_bboxs, old_bboxs=old_bboxs, old_areas_cats=old_areas_cats, new_areas_cats=new_areas_cats, ) return nlines def MoveSelectedVertex(self, point, move): """Move selected vertex of the line :param point: location point :param move: x,y direction :return: id of new feature :return: 0 vertex not moved (not found, line is not selected) :return: -1 on error """ if not self._checkMap(): return -1 if len(self._display.selected["ids"]) != 1: return -1 # move only first found vertex in bbox poList = self._display.GetSelectedIList() if self.emit_signals: cList = poList.contents old_bboxs = [self._getBbox(cList.value[0])] old_areas_cats = [self._getLineAreasCategories(cList.value[0])] Vect_set_updated(self.poMapInfo, 1) n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo) Vect_reset_line(self.poPoints) Vect_append_point(self.poPoints, point[0], point[1], 0.0) moved = Vedit_move_vertex( self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None), poList, self.poPoints, self._display.GetThreshold(type="selectThresh"), self._display.GetThreshold(), move[0], move[1], 0.0, 1, self._getSnapMode(), ) Vect_destroy_list(poList) if moved > 0 and self.emit_signals: n_up_lines = Vect_get_num_updated_lines(self.poMapInfo) new_bboxs = [] new_areas_cats = [] for i in range(n_up_lines_old, n_up_lines): new_id = Vect_get_updated_line(self.poMapInfo, i) new_bboxs.append(self._getBbox(new_id)) new_areas_cats.append(self._getLineAreasCategories(new_id)) if moved > 0 and self._settings["breakLines"]: self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo), None) if moved > 0: self._addChangeset() self.toolbar.EnableUndo() if self.emit_signals: self.vertexMoved.emit( new_bboxs=new_bboxs, new_areas_cats=new_areas_cats, old_areas_cats=old_areas_cats, old_bboxs=old_bboxs, ) return moved def AddVertex(self, coords): """Add new vertex to the selected line/boundary on position 'coords' :param coords: coordinates to add vertex :return: id of new feature :return: 0 nothing changed :return: -1 on failure """ added = self._ModifyLineVertex(coords, add=True) if added > 0: self.toolbar.EnableUndo() return added def RemoveVertex(self, coords): """Remove vertex from the selected line/boundary on position 'coords' :param coords: coordinates to remove vertex :return: id of new feature :return: 0 nothing changed :return: -1 on failure """ deleted = self._ModifyLineVertex(coords, add=False) if deleted > 0: self.toolbar.EnableUndo() return deleted def SplitLine(self, point): """Split/break selected line/boundary on given position :param point: point where to split line :return: 1 line modified :return: 0 nothing changed :return: -1 error """ thresh = self._display.GetThreshold("selectThresh") if not self._checkMap(): return -1 poList = self._display.GetSelectedIList() Vect_reset_line(self.poPoints) Vect_append_point(self.poPoints, point[0], point[1], 0.0) ret = Vedit_split_lines(self.poMapInfo, poList, self.poPoints, thresh, None) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def EditLine(self, line, coords): """Edit existing line/boundary :param line: feature id to be modified :param coords: list of coordinates of modified line :return: feature id of new line :return: -1 on error """ if not self._checkMap(): return -1 if len(coords) < 2: self.DeleteSelectedLines() return 0 if not Vect_line_alive(self.poMapInfo, line): self._error.DeadLine(line) return -1 # read original feature ltype = Vect_read_line(self.poMapInfo, None, self.poCats, line) if ltype < 0: self._error.ReadLine(line) return -1 if self.emit_signals: old_bboxs = [self._getBbox(line)] old_areas_cats = [self._getLineAreasCategories(line)] # build feature geometry Vect_reset_line(self.poPoints) for p in coords: Vect_append_point(self.poPoints, p[0], p[1], 0.0) # apply snapping (node or vertex) snap = self._getSnapMode() if snap != NO_SNAP: modeSnap = not (snap == SNAP) Vedit_snap_line( self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None), -1, self.poPoints, self._display.GetThreshold(), modeSnap, ) newline = Vect_rewrite_line( self.poMapInfo, line, ltype, self.poPoints, self.poCats ) if newline > 0 and self.emit_signals: new_geom = [self._getBbox(newline)] new_areas_cats = [self._getLineAreasCategories(newline)] if newline > 0 and self._settings["breakLines"]: self._breakLineAtIntersection(newline, None) if newline > 0: self._addChangeset() self.toolbar.EnableUndo() if self.emit_signals: self.lineEdited.emit( old_bboxs=old_bboxs, old_areas_cats=old_areas_cats, new_bboxs=new_bboxs, new_areas_cats=new_areas_cats, ) return newline def FlipLine(self): """Flip selected lines/boundaries :return: number of modified lines :return: -1 on error """ if not self._checkMap(): return -1 nlines = Vect_get_num_lines(self.poMapInfo) poList = self._display.GetSelectedIList() ret = Vedit_flip_lines(self.poMapInfo, poList) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def MergeLine(self): """Merge selected lines/boundaries :return: number of modified lines :return: -1 on error """ if not self._checkMap(): return -1 poList = self._display.GetSelectedIList() ret = Vedit_merge_lines(self.poMapInfo, poList) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def BreakLine(self): """Break selected lines/boundaries :return: number of modified lines :return: -1 on error """ if not self._checkMap(): return -1 poList = self._display.GetSelectedIList() ret = Vect_break_lines_list(self.poMapInfo, poList, None, GV_LINES, None) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def SnapLine(self): """Snap selected lines/boundaries :return: 0 on success :return: -1 on error """ if not self._checkMap(): return -1 nlines = Vect_get_num_lines(self.poMapInfo) poList = self._display.GetSelectedIList() Vect_snap_lines_list(self.poMapInfo, poList, self._display.GetThreshold(), None) Vect_destroy_list(poList) if nlines < Vect_get_num_lines(self.poMapInfo): self._addChangeset() self.toolbar.EnableUndo() return 0 def ConnectLine(self): """Connect selected lines/boundaries :return: 1 lines connected :return: 0 lines not connected :return: -1 on error """ if not self._checkMap(): return -1 poList = self._display.GetSelectedIList() ret = Vedit_connect_lines(self.poMapInfo, poList, self._display.GetThreshold()) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def CopyLine(self, ids=[]): """Copy features from (background) vector map :param ids: list of line ids to be copied :return: number of copied features :return: -1 on error """ if not self._checkMap(): return -1 nlines = Vect_get_num_lines(self.poMapInfo) poList = self._display.GetSelectedIList(ids) ret = Vedit_copy_lines(self.poMapInfo, self.poBgMapInfo, poList) Vect_destroy_list(poList) if ret > 0 and self.poBgMapInfo and self._settings["breakLines"]: for i in range(1, ret): self._breakLineAtIntersection(nlines + i, None) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def CopyCats(self, fromId, toId, copyAttrb=False): """Copy given categories to objects with id listed in ids :param cats: ids of 'from' feature :param ids: ids of 'to' feature(s) :return: number of modified features :return: -1 on error """ if len(fromId) < 1 or len(toId) < 1: return 0 poCatsFrom = self.poCats poCatsTo = Vect_new_cats_struct() nlines = 0 for fline in fromId: if not Vect_line_alive(self.poMapInfo, fline): continue if Vect_read_line(self.poMapInfo, None, poCatsFrom, fline) < 0: self._error.ReadLine(fline) return -1 for tline in toId: if not Vect_line_alive(self.poMapInfo, tline): continue ltype = Vect_read_line(self.poMapInfo, self.poPoints, poCatsTo, tline) if ltype < 0: self._error.ReadLine(fline) return -1 catsFrom = poCatsFrom.contents for i in range(catsFrom.n_cats): if not copyAttrb: # duplicate category cat = catsFrom.cat[i] else: # duplicate attributes cat = self.cats[catsFrom.field[i]] + 1 self.cats[catsFrom.field[i]] = cat poFi = Vect_get_field(self.poMapInfo, catsFrom.field[i]) if not poFi: self._error.DbLink(i) return -1 fi = poFi.contents driver = db_start_driver(fi.driver) if not driver: self._error.Driver(fi.driver) return -1 handle = dbHandle() db_init_handle(byref(handle)) db_set_handle(byref(handle), fi.database, None) if db_open_database(driver, byref(handle)) != DB_OK: db_shutdown_driver(driver) self._error.Database(fi.driver, fi.database) return -1 stmt = dbString() db_init_string(byref(stmt)) db_set_string( byref(stmt), "SELECT * FROM %s WHERE %s=%d" % (fi.table, fi.key, catsFrom.cat[i]), ) cursor = dbCursor() if ( db_open_select_cursor( driver, byref(stmt), byref(cursor), DB_SEQUENTIAL ) != DB_OK ): db_close_database_shutdown_driver(driver) return -1 table = db_get_cursor_table(byref(cursor)) ncols = db_get_table_number_of_columns(table) sql = "INSERT INTO %s VALUES (" % fi.table # fetch the data more = c_int() while True: if db_fetch(byref(cursor), DB_NEXT, byref(more)) != DB_OK: db_close_database_shutdown_driver(driver) return -1 if not more.value: break value_string = dbString() for col in range(ncols): if col > 0: sql += "," column = db_get_table_column(table, col) if db_get_column_name(column) == fi.key: sql += "%d" % cat continue value = db_get_column_value(column) db_convert_column_value_to_string( column, byref(value_string) ) if db_test_value_isnull(value): sql += "NULL" else: ctype = db_sqltype_to_Ctype( db_get_column_sqltype(column) ) if ctype != DB_C_TYPE_STRING: sql += db_get_string(byref(value_string)) else: sql += "'%s'" % db_get_string( byref(value_string) ) sql += ")" db_set_string(byref(stmt), sql) if db_execute_immediate(driver, byref(stmt)) != DB_OK: db_close_database_shutdown_driver(driver) return -1 db_close_database_shutdown_driver(driver) G_free(poFi) if Vect_cat_set(poCatsTo, catsFrom.field[i], cat) < 1: continue if ( Vect_rewrite_line( self.poMapInfo, tline, ltype, self.poPoints, poCatsTo ) < 0 ): self._error.WriteLine() return -1 nlines += 1 Vect_destroy_cats_struct(poCatsTo) if nlines > 0: self.toolbar.EnableUndo() return nlines def _selectLinesByQueryThresh(self): """Generic method used for SelectLinesByQuery() -- to get threshold value""" thresh = 0.0 if UserSettings.Get(group="vdigit", key="query", subkey="selection") == 0: thresh = UserSettings.Get( group="vdigit", key="queryLength", subkey="thresh" ) if ( UserSettings.Get( group="vdigit", key="queryLength", subkey="than-selection" ) == 0 ): thresh = -1 * thresh else: thresh = UserSettings.Get( group="vdigit", key="queryDangle", subkey="thresh" ) if ( UserSettings.Get( group="vdigit", key="queryDangle", subkey="than-selection" ) == 0 ): thresh = -1 * thresh return thresh def SelectLinesByQuery(self, bbox): """Select features by query .. todo:: layer / 3D :param bbox: bounding box definition """ if not self._checkMap(): return -1 thresh = self._selectLinesByQueryThresh() query = QUERY_UNKNOWN if UserSettings.Get(group="vdigit", key="query", subkey="selection") == 0: query = QUERY_LENGTH else: query = QUERY_DANGLE ftype = GV_POINTS | GV_LINES # TODO: 3D layer = 1 # TODO ids = list() poList = Vect_new_list() coList = poList.contents if UserSettings.Get(group="vdigit", key="query", subkey="box"): Vect_reset_line(self.poPoints) x1, y1 = bbox[0] x2, y2 = bbox[1] z1 = z2 = 0.0 Vect_append_point(self.poPoints, x1, y1, z1) Vect_append_point(self.poPoints, x2, y1, z2) Vect_append_point(self.poPoints, x2, y2, z1) Vect_append_point(self.poPoints, x1, y2, z2) Vect_append_point(self.poPoints, x1, y1, z1) Vect_select_lines_by_polygon( self.poMapInfo, self.poPoints, 0, None, ftype, poList ) if coList.n_values == 0: return ids Vedit_select_by_query(self.poMapInfo, ftype, layer, thresh, query, poList) for i in range(coList.n_values): ids.append(int(coList.value[i])) Debug.msg(3, "IVDigit.SelectLinesByQuery(): lines=%d", coList.n_values) Vect_destroy_list(poList) return ids def IsVector3D(self): """Check if open vector map is 3D""" if not self._checkMap(): return False return Vect_is_3d(self.poMapInfo) def GetLineLength(self, line): """Get line length :param line: feature id :return: line length :return: -1 on error """ if not self._checkMap(): return -1 if not Vect_line_alive(self.poMapInfo, line): return -1 ltype = Vect_read_line(self.poMapInfo, self.poPoints, None, line) if ltype < 0: self._error.ReadLine(line) return ret length = -1 if ltype & GV_LINES: # lines & boundaries length = Vect_line_length(self.poPoints) return length def GetAreaSize(self, centroid): """Get area size :param centroid: centroid id :return: area size :return: -1 on error """ if not self._checkMap(): return -1 ltype = Vect_read_line(self.poMapInfo, None, None, centroid) if ltype < 0: self._error.ReadLine(line) return ret if ltype != GV_CENTROID: return -1 area = Vect_get_centroid_area(self.poMapInfo, centroid) size = -1 if area > 0: if not Vect_area_alive(self.poMapInfo, area): return size size = Vect_get_area_area(self.poMapInfo, area) return size def GetAreaPerimeter(self, centroid): """Get area perimeter :param centroid: centroid id :return: area size :return: -1 on error """ if not self._checkMap(): return -1 ltype = Vect_read_line(self.poMapInfo, None, None, centroid) if ltype < 0: self._error.ReadLine(line) return ret if ltype != GV_CENTROID: return -1 area = Vect_get_centroid_area(self.poMapInfo, centroid) perimeter = -1 if area > 0: if not Vect_area_alive(self.poMapInfo, area): return -1 Vect_get_area_points(self.poMapInfo, area, self.poPoints) perimeter = Vect_area_perimeter(self.poPoints) return perimeter def SetLineCats(self, line, layer, cats, add=True): """Set categories for given line and layer :param line: feature id :param layer: layer number (-1 for first selected line) :param cats: list of categories :param add: if True to add, otherwise do delete categories :return: new feature id (feature need to be rewritten) :return: -1 on error """ if not self._checkMap(): return -1 if line < 1 and len(self._display.selected["ids"]) < 1: return -1 update = False if line == -1: update = True line = self._display.selected["ids"][0] if not Vect_line_alive(self.poMapInfo, line): return -1 ltype = Vect_read_line(self.poMapInfo, self.poPoints, self.poCats, line) if ltype < 0: self._error.ReadLine(line) return -1 for c in cats: if add: Vect_cat_set(self.poCats, layer, c) else: Vect_field_cat_del(self.poCats, layer, c) newline = Vect_rewrite_line( self.poMapInfo, line, ltype, self.poPoints, self.poCats ) if newline > 0: self._addChangeset() self.toolbar.EnableUndo() if update: # update line id since the line was rewritten self._display.selected["ids"][0] = newline return newline def TypeConvForSelectedLines(self): """Feature type conversion for selected objects. Supported conversions: - point <-> centroid - line <-> boundary :return: number of modified features :return: -1 on error """ if not self._checkMap(): return -1 poList = self._display.GetSelectedIList() ret = Vedit_chtype_lines(self.poMapInfo, poList) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def Undo(self, level=-1): """Undo action :param level: levels to undo (0 to revert all) :return: id of current changeset """ changesetLast = len(self.changesets) - 1 if changesetLast < 0: return changesetLast if level < 0 and self.changesetCurrent > changesetLast: self.changesetCurrent = changesetLast elif level == 0: # 0 -> undo all level = -1 * self.changesetCurrent - 1 Debug.msg( 2, "Digit.Undo(): changeset_last=%d, changeset_current=%d, level=%d", changesetLast, self.changesetCurrent, level, ) if level < 0: # undo if self.changesetCurrent + level < -1: return self.changesetCurrent for changeset in range( self.changesetCurrent, self.changesetCurrent + level, -1 ): self._applyChangeset(changeset, undo=True) elif level > 0: # redo if self.changesetCurrent + 1 > changesetLast: return self.changesetCurrent for changeset in range( self.changesetCurrent + 1, self.changesetCurrent + 1 + level ): self._applyChangeset(changeset, undo=False) self.changesetCurrent += level Debug.msg( 2, "Digit.Undo(): changeset_current=%d, changeset_last=%d", self.changesetCurrent, changesetLast, ) self.mapWindow.UpdateMap(render=False) if self.changesetCurrent < 0: # disable undo tool self.toolbar.EnableUndo(False) else: self.toolbar.EnableUndo(True) if self.changesetCurrent < changesetLast: self.toolbar.EnableRedo(True) else: self.toolbar.EnableRedo(False) def ZBulkLines(self, pos1, pos2, start, step): """Z-bulk labeling :param pos1: reference line (start point) :param pos1: reference line (end point) :param start: starting value :param step: step value :return: number of modified lines :return: -1 on error """ if not self._checkMap(): return -1 poList = self._display.GetSelectedIList() ret = Vedit_bulk_labeling( self.poMapInfo, poList, pos1[0], pos1[1], pos2[0], pos2[1], start, step ) Vect_destroy_list(poList) if ret > 0: self._addChangeset() self.toolbar.EnableUndo() return ret def GetDisplay(self): """Get display driver instance""" return self._display def OpenMap(self, name, update=True, tmp=False): """Open vector map for editing :param map: name of vector map to be set up :type map: str :param tmp: True to open temporary vector map """ Debug.msg(3, "AbstractDigit.SetMapName map=%s" % name) if "@" in name: name, mapset = name.split("@") else: mapset = grass.gisenv()["MAPSET"] self.poMapInfo = self._display.OpenMap(str(name), str(mapset), update, tmp) if self.poMapInfo: self.InitCats() return self.poMapInfo def CloseMap(self): """Close currently open vector map""" if not self._checkMap(): return # print extra line before building message sys.stdout.write(os.linesep) # build topology, close map self._display.CloseMap() def InitCats(self): """Initialize categories information :return: 0 on success :return: -1 on error """ self.cats.clear() if not self._checkMap(): return -1 ndblinks = Vect_get_num_dblinks(self.poMapInfo) for i in range(ndblinks): fi = Vect_get_dblink(self.poMapInfo, i).contents if fi: self.cats[fi.number] = None # find max category nfields = Vect_cidx_get_num_fields(self.poMapInfo) Debug.msg(2, "wxDigit.InitCats(): nfields=%d", nfields) for i in range(nfields): field = Vect_cidx_get_field_number(self.poMapInfo, i) ncats = Vect_cidx_get_num_cats_by_index(self.poMapInfo, i) if field <= 0: continue for j in range(ncats): cat = c_int() type = c_int() id = c_int() Vect_cidx_get_cat_by_index( self.poMapInfo, i, j, byref(cat), byref(type), byref(id) ) if field in self.cats: if self.cats[field] is None or cat.value > self.cats[field]: self.cats[field] = cat.value else: self.cats[field] = cat.value Debug.msg( 3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field] ) # set default values for field, cat in six.iteritems(self.cats): if cat is None: self.cats[field] = 0 # first category 1 Debug.msg( 3, "wxDigit.InitCats(): layer=%d, cat=%d", field, self.cats[field] ) def _checkMap(self): """Check if map is open""" if not self.poMapInfo: self._error.NoMap() return False return True def _addFeature(self, ftype, coords, layer, cat, snap, threshold): """Add new feature(s) to the vector map :param ftype: feature type (GV_POINT, GV_LINE, GV_BOUNDARY, ...) :param coords: tuple of coordinates ((x, y), (x, y), ...) :param layer: layer number (-1 for no cat) :param cat: category number :param snap: snap to node/vertex :param threshold: threshold for snapping :return: tuple (number of added features, list of fids) :return: number of features -1 on error """ fids = list() if not self._checkMap(): return (-1, None) is3D = bool(Vect_is_3d(self.poMapInfo)) Debug.msg( 2, "IVDigit._addFeature(): npoints=%d, layer=%d, cat=%d, snap=%d", len(coords), layer, cat, snap, ) if not (ftype & (GV_POINTS | GV_LINES | GV_AREA)): # TODO: 3D self._error.FeatureType(ftype) return (-1, None) # set category Vect_reset_cats(self.poCats) if layer > 0 and ftype != GV_AREA: Vect_cat_set(self.poCats, layer, cat) self.cats[layer] = max(cat, self.cats.get(layer, 1)) # append points Vect_reset_line(self.poPoints) for c in coords: Vect_append_point(self.poPoints, c[0], c[1], 0.0) if ftype & (GV_BOUNDARY | GV_AREA): # close boundary points = self.poPoints.contents last = points.n_points - 1 if self._settings["closeBoundary"]: Vect_append_point(self.poPoints, points.x[0], points.y[0], points.z[0]) elif ( Vect_points_distance( points.x[0], points.y[0], points.z[0], points.x[last], points.y[last], points.z[last], is3D, ) <= threshold ): points.x[last] = points.x[0] points.y[last] = points.y[0] points.z[last] = points.z[0] if snap != NO_SNAP: # apply snapping (node or vertex) modeSnap = not (snap == SNAP) Vedit_snap_line( self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None), -1, self.poPoints, threshold, modeSnap, ) if ftype == GV_AREA: ltype = GV_BOUNDARY else: ltype = ftype newline = Vect_write_line(self.poMapInfo, ltype, self.poPoints, self.poCats) if newline < 0: self._error.WriteLine() return (-1, None) fids.append(newline) # add centroids for left/right area if ftype & GV_AREA: left = right = -1 bpoints = Vect_new_line_struct() cleft = c_int() cright = c_int() Vect_get_line_areas(self.poMapInfo, newline, byref(cleft), byref(cright)) left = cleft.value right = cright.value Debug.msg(3, "IVDigit._addFeature(): area - left=%d right=%d", left, right) # check if area exists and has no centroid inside if layer > 0 and (left > 0 or right > 0): Vect_cat_set(self.poCats, layer, cat) self.cats[layer] = max(cat, self.cats.get(layer, 0)) x = c_double() y = c_double() if left > 0 and Vect_get_area_centroid(self.poMapInfo, left) == 0: # if Vect_get_area_points(self.poMapInfo, left, bpoints) > 0 and # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0: if ( Vect_get_point_in_area(self.poMapInfo, left, byref(x), byref(y)) == 0 ): Vect_reset_line(bpoints) Vect_append_point(bpoints, x.value, y.value, 0.0) newc = Vect_write_line( self.poMapInfo, GV_CENTROID, bpoints, self.poCats ) if newc < 0: self._error.WriteLine() return (len(fids), fids) else: fids.append(newc) if right > 0 and Vect_get_area_centroid(self.poMapInfo, right) == 0: # if Vect_get_area_points(byref(self.poMapInfo), right, bpoints) > 0 and # Vect_find_poly_centroid(bpoints, byref(x), byref(y)) == 0: if ( Vect_get_point_in_area(self.poMapInfo, right, byref(x), byref(y)) == 0 ): Vect_reset_line(bpoints) Vect_append_point(bpoints, x.value, y.value, 0.0) newc = Vect_write_line( self.poMapInfo, GV_CENTROID, bpoints, self.poCats ) if newc < 0: self._error.WriteLine() return len(fids, fids) else: fids.append(newc) Vect_destroy_line_struct(bpoints) # break line or boundary at intersection if self._settings["breakLines"]: self._breakLineAtIntersection(newline, self.poPoints) self._addChangeset() if ftype & GV_AREA: # len(fids) == 1 -> no new area return (len(fids) - 1, fids) return (len(fids), fids) def _ModifyLineVertex(self, coords, add=True): """Add or remove vertex Shape of line/boundary is not changed when adding new vertex. :param coords: coordinates of point :param add: True to add, False to remove :type add: bool :return: 1 on success :return: 0 nothing changed :return: -1 error """ if not self._checkMap(): return -1 selected = self._display.selected if len(selected["ids"]) != 1: return 0 poList = self._display.GetSelectedIList() if self.emit_signals: cList = poList.contents old_bboxs = [self._getBbox(cList.value[0])] old_areas_cats = [self._getLineAreasCategories(cList.value[0])] Vect_set_updated(self.poMapInfo, 1) n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo) Vect_reset_line(self.poPoints) Vect_append_point(self.poPoints, coords[0], coords[1], 0.0) thresh = self._display.GetThreshold(type="selectThresh") if add: ret = Vedit_add_vertex(self.poMapInfo, poList, self.poPoints, thresh) else: ret = Vedit_remove_vertex(self.poMapInfo, poList, self.poPoints, thresh) Vect_destroy_list(poList) if ret > 0 and self.emit_signals: new_bboxs = [] new_areas_cats = [] n_up_lines = Vect_get_num_updated_lines(self.poMapInfo) for i in range(n_up_lines_old, n_up_lines): new_id = Vect_get_updated_line(self.poMapInfo, i) new_areas_cats.append(self._getLineAreasCategories(new_id)) new_bboxs.append(self._getBbox(new_id)) if not add and ret > 0 and self._settings["breakLines"]: self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo), None) if ret > 0: self._addChangeset() if ret > 0 and self.emit_signals: if add: self.vertexAdded.emit(old_bboxs=old_bboxs, new_bboxs=new_bboxs) else: self.vertexRemoved.emit( old_bboxs=old_bboxs, new_bboxs=new_bboxs, old_areas_cats=old_areas_cats, new_areas_cats=new_areas_cats, ) return 1 def GetLineCats(self, line): """Get list of layer/category(ies) for selected feature. :param line: feature id (-1 for first selected feature) :return: list of layer/cats """ ret = dict() if not self._checkMap(): return ret if line == -1 and len(self._display.selected["ids"]) < 1: return ret if line == -1: line = self._display.selected["ids"][0] if not Vect_line_alive(self.poMapInfo, line): self._error.DeadLine(line) return ret if Vect_read_line(self.poMapInfo, None, self.poCats, line) < 0: self._error.ReadLine(line) return ret cats = self.poCats.contents for i in range(cats.n_cats): field = cats.field[i] if field not in ret: ret[field] = list() ret[field].append(cats.cat[i]) return ret def GetLayers(self): """Get list of layers Requires self.InitCats() to be called. :return: list of layers """ return self.cats.keys() def UpdateSettings(self): """Update digit (and display) settings""" self._display.UpdateSettings() self._settings["breakLines"] = bool( UserSettings.Get(group="vdigit", key="breakLines", subkey="enabled") ) self._settings["closeBoundary"] = bool( UserSettings.Get(group="vdigit", key="closeBoundary", subkey="enabled") ) def SetCategory(self): """Update self.cats based on settings""" sel = UserSettings.Get(group="vdigit", key="categoryMode", subkey="selection") cat = None if sel == 0: # next to usep cat = self._setCategoryNextToUse() elif sel == 1: cat = UserSettings.Get(group="vdigit", key="category", subkey="value") if cat: layer = UserSettings.Get(group="vdigit", key="layer", subkey="value") self.cats[layer] = cat return cat def _setCategoryNextToUse(self): """Find maximum category number for the given layer and update the settings :return: category to be used """ # get max category number for given layer and update the settings layer = UserSettings.Get(group="vdigit", key="layer", subkey="value") cat = self.cats.get(layer, 0) + 1 UserSettings.Set(group="vdigit", key="category", subkey="value", value=cat) Debug.msg(1, "IVDigit._setCategoryNextToUse(): cat=%d", cat) return cat def SelectLinesFromBackgroundMap(self, bbox): """Select features from background map :param bbox: bounding box definition :return: list of selected feature ids """ # try select features by box first if self._display.SelectLinesByBox(bbox, poMapInfo=self.poBgMapInfo) < 1: self._display.SelectLineByPoint(bbox[0], poMapInfo=self.poBgMapInfo)["line"] return self._display.selected["ids"] def GetUndoLevel(self): """Get undo level (number of active changesets) Note: Changesets starts with 0 """ return self.changesetCurrent def GetFeatureType(self): """Get feature type for OGR layers :return: feature type as string (point, linestring, polygon) :return: None for native format """ topoFormat = Vect_get_finfo_topology_info(self.poMapInfo, None, None, None) if topoFormat == GV_TOPO_PSEUDO: return Vect_get_finfo_geometry_type(self.poMapInfo) return ""