class PointTool(TouchMapTool): """ A basic point tool that can be connected to actions in order to handle point based actions. """ geometryComplete = pyqtSignal(QgsGeometry) error = pyqtSignal(str) def __init__(self, canvas, config=None): super(PointTool, self).__init__(canvas) self.canvas = canvas if not config: self.config = {} else: self.config = config self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #FF0000", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) self.captureaction = CaptureAction(self, 'point') self.gpscapture = GPSCaptureAction(self, 'point') self.gpscapture.triggered.connect(self.addatgps) self.snapper = self.canvas.snappingUtils() self.pointband = RubberBand(self.canvas, QgsWkbTypes.PointGeometry) self.startcolour = QColor.fromRgb(0, 0, 255, 100) self.pointband.setColor(self.startcolour) self.pointband.setIconSize(20) self.pointband.addPoint(QgsPointXY(0, 0)) self.pointband.hide() @property def actions(self): return [self.captureaction, self.gpscapture] def canvasPressEvent(self, event): self.startpoint = event.pos() def canvasReleaseEvent(self, event: QgsMapMouseEvent): if self.pinching: return if self.dragging: diff = self.startpoint - event.pos() if not abs(diff.x()) < 10 and not abs(diff.y()) < 10: super(PointTool, self).canvasReleaseEvent(event) return point = event.snapPoint() self.geometryComplete.emit(QgsGeometry.fromPointXY(point)) def canvasMoveEvent(self, event: QgsMapMouseEvent): point = event.snapPoint() self.pointband.movePoint(point) self.pointband.show() def addatgps(self): location = GPS.position self.geometryComplete.emit(QgsGeometry.fromPointXY(location)) def activate(self): """ Set the tool as the active tool in the canvas. @note: Should be moved out into qmap.py and just expose a cursor to be used """ self.pointband.reset(QgsWkbTypes.PointGeometry) self.pointband.addPoint(QgsPointXY(0, 0)) self.canvas.setCursor(self.cursor) def deactivate(self): """ Deactive the tool. """ self.clearBand() def clearBand(self): self.pointband.reset(QgsWkbTypes.PointGeometry) def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return False def setEditMode(self, enabled, geom, feature): self.captureaction.setEditMode(enabled)
class PointTool(TouchMapTool): """ A basic point tool that can be connected to actions in order to handle point based actions. """ geometryComplete = pyqtSignal(QgsGeometry) error = pyqtSignal(str) def __init__(self, canvas, config=None): super(PointTool, self).__init__(canvas) self.canvas = canvas if not config: self.config = {} else: self.config = config self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #FF0000", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) self.captureaction = CaptureAction(self, 'point') self.gpscapture = GPSCaptureAction(self, 'point') self.gpscapture.triggered.connect(self.add_point_avg) GPS.gpsposition.connect(self.update_button_action) self.snapper = self.canvas.snappingUtils() self.pointband = RubberBand(self.canvas, QgsWkbTypes.PointGeometry) self.startcolour = QColor.fromRgb(0, 0, 255, 100) self.pointband.setColor(self.startcolour) self.pointband.setIconSize(20) self.pointband.addPoint(QgsPointXY(0, 0)) self.pointband.hide() @property def actions(self): return [self.captureaction, self.gpscapture] def canvasPressEvent(self, event): self.startpoint = event.pos() def canvasReleaseEvent(self, event: QgsMapMouseEvent): if self.pinching: return if self.dragging: diff = self.startpoint - event.pos() if not abs(diff.x()) < 10 and not abs(diff.y()) < 10: super(PointTool, self).canvasReleaseEvent(event) return point = QgsPoint(event.snapPoint()) point.addZValue(0) self.geometryComplete.emit(QgsGeometry(point)) def canvasMoveEvent(self, event: QgsMapMouseEvent): point = event.snapPoint() self.pointband.movePoint(point) self.pointband.show() # --- averaging ----------------------------------------------------------- def update_button_action(self): averaging = roam.config.settings.get('gps_averaging', True) in_action = roam.config.settings.get('gps_averaging_in_action', True) if averaging and in_action: self.captureaction.setEnabled(False) self.gpscapture.setIcon(QIcon(":/icons/pause")) else: self.captureaction.setEnabled(True) geomtype = self.gpscapture.geomtype self.gpscapture.setIcon(QIcon( ":/icons/gpsadd-{}".format(geomtype))) def add_point_avg(self): # if turned on if roam.config.settings.get('gps_averaging', True): # if currently happening if roam.config.settings.get('gps_averaging_in_action', True): # start -> stop # time to do some averaging average_point = GPS.average_func(GPS.gpspoints) point = QgsPoint(average_point[0], average_point[1], average_point[2]) self.geometryComplete.emit(QgsGeometry(point)) # default settings vertex_or_point = '' in_action = False start_time = '0:00:00' roam.config.settings['gps_averaging_measurements'] = 0 else: # stop -> start vertex_or_point = 'point' in_action = True start_time = datetime.now() roam.config.settings['gps_vertex_or_point'] = vertex_or_point roam.config.settings['gps_averaging_in_action'] = in_action roam.config.settings['gps_averaging_start_time'] = start_time roam.config.save() else: self.addatgps() # ------------------------------------------------------------------------- def addatgps(self): self.geometryComplete.emit(QgsGeometry(GPS.position)) def activate(self): """ Set the tool as the active tool in the canvas. @note: Should be moved out into qmap.py and just expose a cursor to be used """ self.pointband.reset(QgsWkbTypes.PointGeometry) self.pointband.addPoint(QgsPointXY(0, 0)) self.canvas.setCursor(self.cursor) def deactivate(self): """ Deactive the tool. """ self.clearBand() def clearBand(self): self.pointband.reset(QgsWkbTypes.PointGeometry) def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return False def setEditMode(self, enabled, geom, feature): self.captureaction.setEditMode(enabled)
class PolylineTool(QgsMapToolEdit): mouseClicked = pyqtSignal(QgsPoint) geometryComplete = pyqtSignal(QgsGeometry) error = pyqtSignal(str) MODE = "POLYLINE" def __init__(self, canvas, config=None): super(PolylineTool, self).__init__(canvas) self.logger = roam.utils.logger if not config: self.config = {} else: self.config = config self.geom = None self.minpoints = 2 self.editmode = False self.editvertex = None self.points = [] self.is_tracking = False self.canvas = canvas self.valid_color = QColor.fromRgb(32, 218, 45, 100) self.invalid_color = QColor.fromRgb(216, 26, 96, 100) self.startcolour = self.valid_color self.editcolour = self.valid_color self.band = RubberBand(self.canvas, QgsWkbTypes.LineGeometry, width=5, iconsize=20) self.band.setColor(self.startcolour) self.errorband = RubberBand(self.canvas, QgsWkbTypes.LineGeometry, width=5, iconsize=20) self.errorband.setColor(QColor.fromRgb(64, 64, 64, 90)) self.errorlocations = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry) self.errorlocations.setColor(self.invalid_color) self.errorlocations.setIconSize(20) self.errorlocations.setIcon(QgsRubberBand.ICON_BOX) self.pointband = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry) self.pointband.setColor(self.startcolour) self.pointband.setIconSize(20) self.capturing = False self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #FF0000", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) self.captureaction = CaptureAction(self, "line", text="Digitize") self.captureaction.toggled.connect(self.update_state) self.trackingaction = GPSTrackingAction(self) self.endcaptureaction = EndCaptureAction(self) self.undoaction = UndoAction(self) self.undoaction.setEnabled(False) self.undoaction.triggered.connect(self.undo) self.trackingaction.setEnabled(False) self.trackingaction.toggled.connect(self.set_tracking) self.captureaction.toggled.connect(self.update_end_capture_state) self.trackingaction.toggled.connect(self.update_end_capture_state) self.endcaptureaction.setEnabled(False) self.endcaptureaction.triggered.connect(self.endcapture) self.gpscapture = GPSCaptureAction(self, "line") self.gpscapture.setText("Add Vertex") self.gpscapture.triggered.connect(self.add_vertex) self.timer = QTimer() GPS.gpsfixed.connect(self.update_tracking_button) GPS.gpsdisconnected.connect(self.update_tracking_button_disconnect) RoamEvents.selectioncleared.connect(self.selection_updated) RoamEvents.selectionchanged.connect(self.selection_updated) self.snapping = True self.active_color = self.startcolour def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.reset() if event.key() == Qt.Key_S: self.toggle_snapping() RoamEvents.snappingChanged.emit(self.snapping) def setSnapping(self, snapping): self.snapping = snapping def toggle_snapping(self): self.snapping = not self.snapping def selection_updated(self, *args): if self.editmode: self.reset() self.setEditMode(False, None, None) def update_end_capture_state(self, *args): if self.trackingaction.isChecked() and self.captureaction.isChecked(): self.endcaptureaction.setEnabled(self.capturing) self.undoaction.setEnabled(self.capturing) def update_tracking_button_disconnect(self): self.trackingaction.setEnabled(False) self.captureaction.toggle() def update_tracking_button(self, gps_fixed, info): self.trackingaction.setEnabled(gps_fixed) def update_state(self, toggled): if self.is_tracking: self.set_tracking(False) def set_tracking(self, enable_tracking): if not enable_tracking and not self.captureaction.isChecked(): # Some other tool is grabbing us so we will keep trakcing return if enable_tracking: self.captureaction.setIcon(QIcon(":/icons/pause")) self.captureaction.setText("Pause") gpsettings = roam.config.settings.get("gps", {}) config = gpsettings.get('tracking', {"time", 1}) if "time" in config: self.band.removeLastPoint() self.timer.timeout.connect(self.add_vertex) value = config['time'] value *= 1000 self.timer.start(value) self.capturing = True elif "distance" in config: self.band.removeLastPoint() value = config['distance'] self.add_point(GPS.position) GPS.gpsposition.connect(self.track_gps_location_changed) else: self.captureaction.setIcon(self.captureaction._defaulticon) self.captureaction.setText("Digitize") if self.capturing: point = self.pointband.getPoint( 0, self.pointband.numberOfVertices() - 1) if point: self.band.addPoint(point) try: self.timer.stop() self.timer.timeout.disconnect(self.add_vertex) except TypeError: pass try: GPS.gpsposition.disconnect(self.track_gps_location_changed) except TypeError: pass self.is_tracking = enable_tracking self.trackingaction.set_text(enable_tracking) @property def max_distance(self): gpsettings = roam.config.settings.get("gps", {}) config = gpsettings.get('tracking', {"time", 1}) value = config['distance'] return value def track_gps_location_changed(self, point, info): lastpoint = self.pointband.getPoint( 0, self.pointband.numberOfVertices() - 1) distance = point.sqrDist(lastpoint) if distance >= self.max_distance: self.add_point(point) @property def actions(self): return [ self.captureaction, self.trackingaction, self.gpscapture, self.endcaptureaction, self.undoaction ] def add_vertex(self): location = GPS.position self.remove_last_point() self.add_point(location) # Add an extra point for the move band self.band.addPoint(location) def remove_last_vertex(self): self.remove_last_point() self.band.removeLastPoint(doUpdate=True) def canvasPressEvent(self, event): geom = self.band.asGeometry() if not geom: return point = point_from_event(event, self.snapping) if self.editmode: layer = self.currentVectorLayer() event.snapToGrid(layer.geometryOptions().geometryPrecision(), layer.crs()) tol = QgsTolerance.vertexSearchRadius(self.canvas.mapSettings()) loc = self.canvas.snappingUtils().locatorForLayer(layer) matches = loc.verticesInRect(point, tol) if matches: for match in matches: if match.featureId() != self.feature.id(): continue self.editpart = 0 self.editvertex = match.vertexIndex() break else: self.editvertex = None self.editpart = 0 def vertex_at_point(self, point): layer = self.currentVectorLayer() tol = QgsTolerance.vertexSearchRadius(self.canvas.mapSettings()) loc = self.canvas.snappingUtils().locatorForLayer(layer) matches = loc.verticesInRect(point, tol) return matches def canvasReleaseEvent(self, event: QgsMapMouseEvent): if event.button() == Qt.RightButton: self.endcapture() return if not self.editmode: point = event.snapPoint() self.add_point(point) else: self.editvertex = None def canvasMoveEvent(self, event: QgsMapMouseEvent): if self.is_tracking: return point = point_from_event(event, self.snapping) if not self.editmode: self.pointband.movePoint(point) if self.capturing: self.band.movePoint(point) if self.editmode and self.editvertex is not None: found, vertexid = self.geom.vertexIdFromVertexNr(self.editvertex) self.geom.get().moveVertex(vertexid, QgsPoint(point)) self.currentVectorLayer().triggerRepaint() self.feature.setGeometry(self.geom) self.currentVectorLayer().updateFeature(self.feature) self.pointband.setToGeometry(self.toMultiPoint(self.geom), self.currentVectorLayer()) self.band.setToGeometry(self.geom, self.currentVectorLayer()) self.update_valid_state() @property def is_polygon_mode(self): """ Returns true if this tool is in polygon mode :note: This is a gross hack to avoid a import issue until it's refactored :return: """ return self.MODE == "POLYGON" def update_valid_state(self): geom = self.band.asGeometry() if geom and self.band.numberOfVertices() > 0: if self.has_errors(): self.set_band_color(self.invalid_color) else: if not self.editmode: self.set_band_color(self.startcolour) else: self.set_band_color(self.editcolour) @property def active_color(self): return self._active_color @active_color.setter def active_color(self, color): self._active_color = color def has_errors(self): if self.geom is None: geom = self.band.asGeometry() else: geom = self.geom errors = geom.validateGeometry() skippable = ["duplicate node", "Geometry has"] othererrors = [] def is_safe(message): for safe in skippable: if safe in message: return True return False # We need to remove errors that are "ok" and not really that bad # We are harder on what is considered valid for polygons. if self.is_polygon_mode: for error in errors: if not is_safe(error.what()): othererrors.append(error) if self.node_count < self.minpoints: error = QgsGeometry.Error("Number of nodes < {0}".format( self.minpoints)) othererrors.append(error) return othererrors @property def node_count(self): if self.editmode: return self.band.numberOfVertices() else: return self.band.numberOfVertices() - 1 def add_point(self, point): self.points.append(point) self.band.addPoint(point) self.pointband.addPoint(point) self.capturing = True self.endcaptureaction.setEnabled(True) self.undoaction.setEnabled(True) def remove_last_point(self): if len(self.points) > 1: del self.points[-1] self.band.removeLastPoint(doUpdate=True) self.pointband.removeLastPoint(doUpdate=True) def get_geometry(self): if self.geom is None: return self.band.asGeometry() else: return self.geom def endinvalidcapture(self, errors): self.errorband.setToGeometry(self.get_geometry(), self.currentVectorLayer()) for error in errors: if error.hasWhere(): self.errorlocations.addPoint(error.where()) self.capturing = False self.set_tracking(False) self.captureaction.setChecked(True) self.undoaction.setEnabled(False) self.endcaptureaction.setEnabled(False) self.reset() def undo(self): self.remove_last_point() def endcapture(self): if not self.editmode: self.band.removeLastPoint() errors = self.has_errors() if errors and self.config.get("geometry_validation", True): self.error.emit("Invalid geometry. <br>" "Please recapture. Last capture shown in grey <br>" "<h2>Errors</h2> {0}".format("<br>".join( error.what() for error in errors))) self.endinvalidcapture(errors) return self.capturing = False self.set_tracking(False) self.captureaction.setChecked(True) self.undoaction.setEnabled(False) self.endcaptureaction.setEnabled(False) self.clearErrors() self.geometryComplete.emit(self.get_geometry()) def clearErrors(self): self.errorband.reset(QgsWkbTypes.LineGeometry) self.errorlocations.reset(QgsWkbTypes.PointGeometry) def clearBand(self): self.reset() def reset(self, *args): self.band.reset(QgsWkbTypes.LineGeometry) self.pointband.reset(QgsWkbTypes.PointGeometry) self.capturing = False self.set_tracking(False) self.undoaction.setEnabled(False) self.endcaptureaction.setEnabled(False) def activate(self): self.pointband.reset(QgsWkbTypes.PointGeometry) self.pointband.addPoint(QgsPointXY(0, 0)) self.canvas.setCursor(self.cursor) def deactivate(self): """ Deactive the tool. """ self.clearBand() def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True def set_band_color(self, color): self.active_color = color self.band.setColor(color) self.pointband.setColor(color) def setEditMode(self, enabled, geom, feature): self.reset() self.editmode = enabled if self.geom != geom: self.clearErrors() self.geom = geom self.feature = feature if self.editmode: self.set_band_color(self.editcolour) self.pointband.setToGeometry(self.toMultiPoint(geom), self.currentVectorLayer()) self.pointband.setVisible(True) self.band.setToGeometry(geom, self.currentVectorLayer()) else: self.set_band_color(self.startcolour) self.endcaptureaction.setEnabled(self.editmode) self.endcaptureaction.setEditing(enabled) self.captureaction.setEditMode(enabled) def toMultiPoint(self, geom): points = QgsMultiPoint() for count, v in enumerate(geom.vertices()): points.addGeometry(v.clone()) newgeom = QgsGeometry(points) return newgeom