def startFlashFeature(self, featureGeometry, layerCrs): geomType = QgsWkbTypes.geometryType(featureGeometry.wkbType()) rb = QgsRubberBand(iface.mapCanvas(), geomType) rb.addGeometry( featureGeometry, layerCrs ) flashes=3 duration=500 if geomType == QgsWkbTypes.LineGeometry or geomType == QgsWkbTypes.PointGeometry: rb.setWidth( 2 ) rb.setSecondaryStrokeColor( QColor( 255, 255, 255 ) ) if geomType == QgsWkbTypes.PointGeometry : rb.setIcon( QgsRubberBand.ICON_CIRCLE ) startColor = QColor(255, 0, 0, 255) startColor.setAlpha( 255 ) endColor = QColor(255, 0, 0, 0) endColor.setAlpha( 0 ) self.animation = QVariantAnimation( iface.mapCanvas() ) self.animation.finished.connect(lambda a=self.animation, b=rb: self.finishedAnimation(a, b)) self.animation.valueChanged.connect(lambda value, a=self.animation, b=rb, c=geomType: self.valueChangedAnimation(value, a, b, c)) self.animation.setDuration( duration * flashes ) self.animation.setStartValue( endColor ) midStep = 0.2 / flashes for i in range(flashes): start = float(i / flashes) self.animation.setKeyValueAt( start + midStep, startColor ) end = float(( i + 1 ) / flashes) if not(end == 1.0): self.animation.setKeyValueAt( end, endColor ) self.animation.setEndValue( endColor ) self.animation.start()
class RubberBandPolygon(QgsMapTool): def __init__(self, canvas): QgsMapTool.__init__(self, canvas) self.mCanvas = canvas self.mRubberBand = None self.mRubberBand0 = QgsRubberBand(self.mCanvas, QGis.Polygon) self.mCursor = Qt.ArrowCursor self.mFillColor = QColor(254, 178, 76, 63) self.mBorderColour = QColor(254, 58, 29, 100) self.mRubberBand0.setBorderColor(self.mBorderColour) self.polygonGeom = None self.drawFlag = False # self.constructionLayer = constructionLayer def canvasPressEvent(self, e): if (self.mRubberBand == None): self.mRubberBand0.reset(QGis.Polygon) # define._canvas.clearCache () self.mRubberBand = QgsRubberBand(self.mCanvas, QGis.Polygon) self.mRubberBand0 = QgsRubberBand(self.mCanvas, QGis.Polygon) self.mRubberBand.setFillColor(self.mFillColor) self.mRubberBand.setBorderColor(self.mBorderColour) self.mRubberBand0.setFillColor(self.mFillColor) self.mRubberBand0.setBorderColor(self.mBorderColour) if (e.button() == Qt.LeftButton): self.mRubberBand.addPoint(self.toMapCoordinates(e.pos())) else: if (self.mRubberBand.numberOfVertices() > 2): self.polygonGeom = self.mRubberBand.asGeometry() else: return # QgsMapToolSelectUtils.setSelectFeatures( self.mCanvas, polygonGeom, e ) self.mRubberBand.reset(QGis.Polygon) self.mRubberBand0.addGeometry(self.polygonGeom, None) self.mRubberBand0.show() self.mRubberBand = None self.emit(SIGNAL("outputResult"), self.polygonGeom) def canvasMoveEvent(self, e): pass if (self.mRubberBand == None): return if (self.mRubberBand.numberOfVertices() > 0): self.mRubberBand.removeLastPoint(0) self.mRubberBand.addPoint(self.toMapCoordinates(e.pos())) def deactivate(self): # self.rubberBand.reset(QGis.Point) QgsMapTool.deactivate(self) self.emit(SIGNAL("deactivated()"))
def showCrossedLines(self, point): currExt = iface.mapCanvas().extent() leftPt = QgsPoint(currExt.xMinimum(), point.y()) rightPt = QgsPoint(currExt.xMaximum(), point.y()) topPt = QgsPoint(point.x(), currExt.yMaximum()) bottomPt = QgsPoint(point.x(), currExt.yMinimum()) horizLine = QgsGeometry.fromPolyline([leftPt, rightPt]) vertLine = QgsGeometry.fromPolyline([topPt, bottomPt]) crossRb = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.LineGeometry) crossRb.setColor(Qt.red) crossRb.setWidth(2) crossRb.addGeometry(horizLine, None) crossRb.addGeometry(vertLine, None) QTimer.singleShot(1500, lambda r=crossRb: r.reset())
def highlight(self): curr_ext = self.canvas.extent() left_point = QgsPoint(curr_ext.xMinimum(), curr_ext.center().y()) right_point = QgsPoint(curr_ext.xMaximum(), curr_ext.center().y()) top_point = QgsPoint(curr_ext.center().x(), curr_ext.yMaximum()) bottom_point = QgsPoint(curr_ext.center().x(), curr_ext.yMinimum()) horiz_line = QgsGeometry.fromPolyline([left_point, right_point]) vert_line = QgsGeometry.fromPolyline([top_point, bottom_point]) cross_rb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) cross_rb.setColor(QColor(255, 0, 0)) cross_rb.reset(QgsWkbTypes.LineGeometry) cross_rb.addGeometry(horiz_line, None) cross_rb.addGeometry(vert_line, None) QTimer.singleShot(600, cross_rb.reset) self.canvas.refresh()
class MoveTool(QgsMapToolAdvancedDigitizing): """ Map tool class to move or copy an object """ def __init__(self, iface): """ Constructor :param iface: interface """ QgsMapToolAdvancedDigitizing.__init__(self, iface.mapCanvas(), iface.cadDockWidget()) self.__iface = iface self.icon_path = ':/plugins/VDLTools/icons/move_icon.png' self.text = QCoreApplication.translate("VDLTools", "Move/Copy a feature") self.setCursor(Qt.ArrowCursor) self.__isEditing = False self.__findVertex = False self.__onMove = False self.__layer = None self.__confDlg = None self.__lastFeatureId = None self.__selectedFeature = None self.__rubberBand = None self.__rubberSnap = None self.__newFeature = None self.__selectedVertex = None def activate(self): """ When the action is selected """ QgsMapToolAdvancedDigitizing.activate(self) if self.__layer.geometryType() == QGis.Point: self.setMode(self.CaptureLine) else: self.setMode(self.CaptureNone) def deactivate(self): """ When the action is deselected """ self.__cancel() QgsMapToolAdvancedDigitizing.deactivate(self) def toolName(self): """ To get the tool name :return: tool name """ return QCoreApplication.translate("VDLTools", "Move/Copy") def startEditing(self): """ To set the action as enable, as the layer is editable """ self.action().setEnabled(True) Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer.editingStopped.connect(self.stopEditing) def stopEditing(self): """ To set the action as disable, as the layer is not editable """ self.action().setEnabled(False) Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) self.__layer.editingStarted.connect(self.startEditing) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() def setTool(self): """ To set the current tool as this one """ self.canvas().setMapTool(self) def __cancel(self): """ To cancel used variables """ if self.__rubberBand is not None: self.canvas().scene().removeItem(self.__rubberBand) self.__rubberBand.reset() self.__rubberBand = None if self.__rubberSnap is not None: self.canvas().scene().removeItem(self.__rubberSnap) self.__rubberSnap.reset() self.__rubberSnap = None self.__isEditing = False self.__findVertex = False self.__onMove = False self.__lastFeatureId = None self.__selectedFeature = None self.__confDlg = None self.__newFeature = None self.__selectedVertex = None self.__layer.removeSelection() if self.__layer.geometryType() == QGis.Point: self.setMode(self.CaptureLine) else: self.setMode(self.CaptureNone) def __removeLayer(self): """ To remove the current working layer """ if self.__layer is not None: if self.__layer.isEditable(): Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) else: Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer = None def setEnable(self, layer): """ To check if we can enable the action for the selected layer :param layer: selected layer """ if layer is not None and layer.type() == QgsMapLayer.VectorLayer: if layer == self.__layer: return if self.__layer is not None: if self.__layer.isEditable(): Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) else: Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer = layer if self.__layer.geometryType() == QGis.Point: self.setMode(self.CaptureLine) else: self.setMode(self.CaptureNone) if self.__layer.isEditable(): self.action().setEnabled(True) self.__layer.editingStopped.connect(self.stopEditing) else: self.action().setEnabled(False) self.__layer.editingStarted.connect(self.startEditing) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() return self.action().setEnabled(False) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() self.__removeLayer() def __pointPreview(self, point): """ To create a point geometry preview (rubberBand) :param point: new position as mapPoint """ point_v2 = GeometryV2.asPointV2(self.__selectedFeature.geometry(), self.__iface) self.__newFeature = QgsPointV2(point.x(), point.y()) self.__newFeature.addZValue(point_v2.z()) self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point) self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.clone()), None) def __linePreview(self, point): """ To create a line geometry preview (rubberBand) :param point: new position as mapPoint """ line_v2, curved = GeometryV2.asLineV2(self.__selectedFeature.geometry(), self.__iface) vertex = QgsPointV2() line_v2.pointAt(self.__selectedVertex, vertex) self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line) dx = vertex.x() - point.x() dy = vertex.y() - point.y() if isinstance(curved, (list, tuple)): self.__newFeature = QgsCompoundCurveV2() for pos in range(line_v2.nCurves()): curve_v2 = self.__newCurve(curved[pos], line_v2.curveAt(pos), dx, dy) self.__newFeature.addCurve(curve_v2) if pos == 0: self.__rubberBand.setToGeometry(QgsGeometry(curve_v2.curveToLine()), None) else: self.__rubberBand.addGeometry(QgsGeometry(curve_v2.curveToLine()), None) else: self.__newFeature = self.__newCurve(curved, line_v2, dx, dy) self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.curveToLine()), None) @staticmethod def __newCurve(curved, line_v2, dx, dy): """ To create a new moved line :param curved: if the line is curved :param line_v2: the original line :param dx: x translation :param dy: y translation :return: the new line """ if curved: newCurve = QgsCircularStringV2() else: newCurve = QgsLineStringV2() points = [] for pos in range(line_v2.numPoints()): x = line_v2.pointN(pos).x() - dx y = line_v2.pointN(pos).y() - dy pt = QgsPointV2(x, y) pt.addZValue(line_v2.pointN(pos).z()) points.append(pt) newCurve.setPoints(points) return newCurve def __polygonPreview(self, point): """ To create a polygon geometry preview (rubberBand) :param point: new position as mapPoint """ polygon_v2, curved = GeometryV2.asPolygonV2(self.__selectedFeature.geometry(), self.__iface) vertex = polygon_v2.vertexAt(GeometryV2.polygonVertexId(polygon_v2, self.__selectedVertex)) dx = vertex.x() - point.x() dy = vertex.y() - point.y() self.__newFeature = QgsCurvePolygonV2() self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line) line_v2 = self.__newCurve(curved[0], polygon_v2.exteriorRing(), dx, dy) self.__newFeature.setExteriorRing(line_v2) self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()), None) for num in range(polygon_v2.numInteriorRings()): line_v2 = self.__newCurve(curved[num+1], polygon_v2.interiorRing(num), dx, dy) self.__newFeature.addInteriorRing(line_v2) self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()), None) def __onConfirmCancel(self): """ When the Cancel button in Move Confirm Dialog is pushed """ self.__confDlg.reject() def __onConfirmMove(self): """ When the Move button in Move Confirm Dialog is pushed """ geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0) self.__layer.changeGeometry(self.__selectedFeature.id(), geometry) self.__confDlg.accept() self.__cancel() def __onConfirmCopy(self): """ When the Copy button in Move Confirm Dialog is pushed """ geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0) feature = QgsFeature(self.__layer.pendingFields()) feature.setGeometry(geometry) primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn() for field in self.__selectedFeature.fields(): if field.name() != primaryKey: feature.setAttribute(field.name(), self.__selectedFeature.attribute(field.name())) if len(self.__selectedFeature.fields()) > 0 and self.__layer.editFormConfig().suppress() != \ QgsEditFormConfig.SuppressOn: self.__iface.openFeatureForm(self.__layer, feature) else: self.__layer.addFeature(feature) self.__confDlg.accept() self.__cancel() def keyReleaseEvent(self, event): """ When keyboard is pressed :param event: keyboard event """ if event.key() == Qt.Key_Escape: self.__cancel() def cadCanvasMoveEvent(self, event): """ When the mouse is moved :param event: mouse event """ if type(event) == QMoveEvent: map_point = self.toMapCoordinates(event.pos()) else: map_point = event.mapPoint() if not self.__isEditing and not self.__findVertex and not self.__onMove: laySettings = QgsSnappingUtils.LayerConfig(self.__layer, QgsPointLocator.All, 10, QgsTolerance.Pixels) f_l = Finder.findClosestFeatureAt(map_point, self.canvas(), [laySettings]) if f_l is not None and self.__lastFeatureId != f_l[0].id(): self.__lastFeatureId = f_l[0].id() self.__layer.setSelectedFeatures([f_l[0].id()]) if f_l is None: self.__layer.removeSelection() self.__lastFeatureId = None elif self.__findVertex: if self.__rubberBand is not None: self.__rubberBand.reset() closest = self.__selectedFeature.geometry().closestVertex(map_point) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) self.__rubberBand.setToGeometry(QgsGeometry().fromPoint(closest[0]), None) elif self.__onMove: if self.__rubberBand is not None: self.__rubberBand.reset() if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(map_point) elif self.__layer.geometryType() == QGis.Line: self.__linePreview(map_point) else: self.__pointPreview(map_point) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) self.__rubberBand.setWidth(2) if self.__layer.geometryType() != QGis.Point: self.__rubberBand.setLineStyle(Qt.DotLine) else: self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(8) if self.__rubberSnap is not None: self.__rubberSnap.reset() else: self.__rubberSnap = QgsRubberBand(self.canvas(), QGis.Point) self.__rubberSnap.setColor(color) self.__rubberSnap.setWidth(2) self.__rubberSnap.setIconSize(20) match = Finder.snap(map_point, self.canvas()) if match.hasVertex() or match.hasEdge(): point = match.point() if match.hasVertex(): if match.layer(): self.__rubberSnap.setIcon(4) else: self.__rubberSnap.setIcon(1) if match.hasEdge(): intersection = Finder.snapCurvedIntersections(point, self.canvas(), self) if intersection is not None: self.__rubberSnap.setIcon(1) point = intersection else: self.__rubberSnap.setIcon(3) self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(point), None) def cadCanvasReleaseEvent(self, event): """ When the mouse is clicked :param event: mouse event """ if not self.__isEditing and not self.__findVertex and not self.__onMove: found_features = self.__layer.selectedFeatures() if len(found_features) > 0: if len(found_features) > 1: self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "One feature at a time"), level=QgsMessageBar.INFO) return self.__selectedFeature = found_features[0] if self.__layer.geometryType() != QGis.Point: self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "Select vertex for moving (ESC to undo)"), level=QgsMessageBar.INFO, duration=3) self.__findVertex = True self.setMode(self.CaptureLine) self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point) else: self.setMode(self.CaptureNone) self.__onMove = True elif self.__findVertex: self.__findVertex = False self.setMode(self.CaptureNone) closest = self.__selectedFeature.geometry().closestVertex(event.mapPoint()) self.__selectedVertex = closest[1] self.__onMove = True elif self.__onMove: self.__onMove = False mapPoint = event.mapPoint() match = Finder.snap(event.mapPoint(), self.canvas()) if match.hasVertex() or match.hasEdge(): mapPoint = match.point() if match.hasEdge(): intersection = Finder.snapCurvedIntersections(mapPoint, self.canvas(), self) if intersection is not None: mapPoint = intersection self.__isEditing = True if self.__rubberBand is not None: self.__rubberBand.reset() if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(mapPoint) elif self.__layer.geometryType() == QGis.Line: self.__linePreview(mapPoint) else: self.__pointPreview(mapPoint) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) if self.__layer.geometryType() != QGis.Point: self.__rubberBand.setWidth(2) self.__rubberBand.setLineStyle(Qt.DotLine) else: self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) self.__confDlg = MoveConfirmDialog() self.__confDlg.rejected.connect(self.__cancel) self.__confDlg.moveButton().clicked.connect(self.__onConfirmMove) self.__confDlg.copyButton().clicked.connect(self.__onConfirmCopy) self.__confDlg.cancelButton().clicked.connect(self.__onConfirmCancel) self.__confDlg.show()
class GeomapfishLocatorFilter(QgsLocatorFilter): USER_AGENT = b'Mozilla/5.0 QGIS GeoMapFish Locator Filter' def __init__(self, iface: QgisInterface = None): super().__init__() self.rubber_band = None self.settings = Settings() self.iface = None self.map_canvas = None self.current_timer = None self.transform = None # only get map_canvas on main thread, not when cloning if iface is not None: self.iface = iface self.map_canvas = iface.mapCanvas() self.map_canvas.destinationCrsChanged.connect( self.create_transform) self.rubber_band = QgsRubberBand(self.map_canvas) self.rubber_band.setColor(QColor(255, 255, 50, 200)) self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE) self.rubber_band.setIconSize(15) self.rubber_band.setWidth(4) self.rubber_band.setBrushStyle(Qt.NoBrush) self.create_transform() def name(self) -> str: return self.__class__.__name__ def clone(self): return GeomapfishLocatorFilter() def displayName(self) -> str: name = self.settings.value("filter_name") if name != '': return name return self.tr('Geomapfish service') def prefix(self) -> str: return 'gmf' def hasConfigWidget(self) -> bool: return True def openConfigWidget(self, parent=None): ConfigDialog(parent).exec_() def create_transform(self): srv_crs_authid = self.settings.value('geomapfish_crs') src_crs = QgsCoordinateReferenceSystem(srv_crs_authid) assert src_crs.isValid() dst_crs = self.map_canvas.mapSettings().destinationCrs() self.transform = QgsCoordinateTransform(src_crs, dst_crs, QgsProject.instance()) @staticmethod def url_with_param(url, params) -> str: url = QUrl(url) q = QUrlQuery(url) for key, value in params.items(): q.addQueryItem(key, value) url.setQuery(q) return url.url() def emit_bad_configuration(self, err=None): result = QgsLocatorResult() result.filter = self result.displayString = self.tr('Locator filter is not configured.') result.description = err if err else self.tr( 'Double-click to configure.') result.userData = FilterNotConfigured result.icon = QgsApplication.getThemeIcon('mIconWarning.svg') self.resultFetched.emit(result) return @pyqtSlot() def clear_results(self): if self.rubber_band: self.rubber_band.reset(QgsWkbTypes.PointGeometry) if self.current_timer is not None: self.current_timer.timeout.disconnect(self.clear_results) self.current_timer.stop() self.current_timer.deleteLater() self.current_timer = None def fetchResults(self, search, context, feedback): try: self.dbg_info("start GMF locator search...") url = self.settings.value('geomapfish_url') if url == "": self.emit_bad_configuration() return params = { 'query': search, 'limit': str(self.settings.value('total_limit')), 'partitionlimit': str(self.settings.value('category_limit')) } if len(search) < 2: return headers = {b'User-Agent': self.USER_AGENT} if self.settings.value('geomapfish_user') != '': user = self.settings.value('geomapfish_user') password = self.settings.value('geomapfish_pass') auth_data = "{}:{}".format(user, password) b64 = QByteArray(auth_data.encode()).toBase64() headers[QByteArray('Authorization'.encode())] = QByteArray( 'Basic '.encode()) + b64 url = self.url_with_param(url, params) self.dbg_info(url) nam = NetworkAccessManager() feedback.canceled.connect(nam.abort) (response, content) = nam.request(url, headers=headers, blocking=True) self.handle_response(response, content) except RequestsExceptionUserAbort: pass except RequestsException as err: self.emit_bad_configuration(str(err)) self.info(err) except Exception as e: self.info(str(e), Qgis.Critical) #exc_type, exc_obj, exc_traceback = sys.exc_info() #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1] #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) #self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) finally: self.finished.emit() def handle_response(self, response, content): try: if response.status_code != 200: self.info("Error with status code: {}".format( response.status_code)) return data = json.loads(content.decode('utf-8')) #self.dbg_info(data) features = data['features'] for f in features: json_geom = json.dumps(f['geometry']) ogr_geom = ogr.CreateGeometryFromJson(json_geom) wkt = ogr_geom.ExportToWkt() geometry = QgsGeometry.fromWkt(wkt) self.dbg_info('---------') self.dbg_info( QgsWkbTypes.geometryDisplayString(geometry.type())) self.dbg_info(f.keys()) self.dbg_info('{} {}'.format(f['properties']['layer_name'], f['properties']['label'])) self.dbg_info(f['bbox']) self.dbg_info(f['geometry']) if geometry is None: continue result = QgsLocatorResult() result.filter = self result.displayString = f['properties']['label'] if Qgis.QGIS_VERSION_INT >= 30100: result.group = self.beautify_group( f['properties']['layer_name']) result.userData = geometry self.resultFetched.emit(result) except Exception as e: self.info(str(e), Qgis.Critical) #exc_type, exc_obj, exc_traceback = sys.exc_info() #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1] #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) # self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def triggerResult(self, result): self.clear_results() if result.userData == FilterNotConfigured: self.openConfigWidget() if self.iface and hasattr(self.iface, 'invalidateLocatorResults'): # from QGIS 3.2 iface has invalidateLocatorResults self.iface.invalidateLocatorResults() return # this should be run in the main thread, i.e. mapCanvas should not be None geometry = result.userData geometry.transform(self.transform) self.rubber_band.reset(geometry.type()) self.rubber_band.addGeometry(geometry, None) rect = geometry.boundingBox() rect.scale(1.5) self.map_canvas.setExtent(rect) self.map_canvas.refresh() self.current_timer = QTimer() self.current_timer.timeout.connect(self.clear_results) self.current_timer.setSingleShot(True) self.current_timer.start(5000) def beautify_group(self, group) -> str: if self.settings.value("remove_leading_digits"): group = re.sub('^[0-9]+', '', group) if self.settings.value("replace_underscore"): group = group.replace("_", " ") if self.settings.value("break_camelcase"): group = self.break_camelcase(group) return group def info(self, msg="", level=Qgis.Info): QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg), 'QgsLocatorFilter', level) def dbg_info(self, msg=""): if DEBUG: self.info(msg) @staticmethod def break_camelcase(identifier) -> str: matches = re.finditer( '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', identifier) return ' '.join([m.group(0) for m in matches])
class AnnotationManager: def __init__(self, iface): locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join(os.path.dirname(__file__), 'i18n', 'annotationManager_{}.qm'.format(locale)) self.translator = None if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) self.iface = iface self.iface.projectRead.connect(self.projectOpen) self.dock = QDockWidget(self.tr('Annotations')) self.manager = QWidget() toolbar = QToolBar() self.annotationList = QListWidget() self.annotationList.setSelectionMode( QAbstractItemView.ExtendedSelection) self.annotationList.itemSelectionChanged.connect(self.selectAnnotation) self.annotationList.itemChanged.connect(self.checkItem) action_refresh = QAction( QIcon(':/plugins/annotationManager/resources/mActionDraw.png'), self.tr('Refresh the annotations list'), self.manager) action_refresh.triggered.connect(self.refreshAnnotations) action_remove = QAction( QIcon( ':/plugins/annotationManager/resources/mActionRemoveAnnotation.png' ), self.tr('Remove the selected annotation'), self.manager) action_remove.triggered.connect(self.removeAnnotation) viewMenu = QMenu() action_showAll = QAction( QIcon(':/plugins/annotationManager/resources/mActionShowAll.png'), self.tr('Show all annotations'), self.manager) action_showAll.triggered.connect(self.showAll) action_hideAll = QAction( QIcon(':/plugins/annotationManager/resources/mActionHideAll.png'), self.tr('Hide all annotations'), self.manager) action_hideAll.triggered.connect(self.hideAll) action_showAllSelected = QAction( QIcon(':/plugins/annotationManager/resources/mActionShowAll.png'), self.tr('Show all selected annotations'), self.manager) action_showAllSelected.triggered.connect(self.showAllSelected) action_hideAllSelected = QAction( QIcon(':/plugins/annotationManager/resources/mActionHideAll.png'), self.tr('Hide all selected annotations'), self.manager) action_hideAllSelected.triggered.connect(self.hideAllSelected) viewMenu.addAction(action_showAll) viewMenu.addAction(action_hideAll) viewMenu.addAction(action_showAllSelected) viewMenu.addAction(action_hideAllSelected) viewButton = QToolButton() viewButton.setIcon( QIcon(':/plugins/annotationManager/resources/mActionShowAll.png')) viewButton.setPopupMode(2) viewButton.setMenu(viewMenu) toolbar.addAction(action_refresh) toolbar.addAction(action_remove) toolbar.addWidget(viewButton) toolbar.setIconSize(QSize(16, 16)) p1_vertical = QVBoxLayout() p1_vertical.setContentsMargins(0, 0, 0, 0) p1_vertical.addWidget(toolbar) p1_vertical.addWidget(self.annotationList) self.manager.setLayout(p1_vertical) self.dock.setWidget(self.manager) self.dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dock) self.rb = QgsRubberBand(self.iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.project = QgsProject.instance() self.annotationManager = self.project.annotationManager() self.annotationManager.annotationAdded.connect(self.refreshAnnotations) self.annotationManager.annotationRemoved.connect( self.refreshAnnotations) def checkItem(self, item): index = self.annotationList.row(item) if item.checkState() == Qt.Checked: self.annotationManager.annotations()[index].setVisible(True) else: self.annotationManager.annotations()[index].setVisible(False) if item.isSelected(): item.setSelected(False) self.rb.reset(QgsWkbTypes.PolygonGeometry) def selectAnnotation(self): self.rb.reset(QgsWkbTypes.PolygonGeometry) self.rb.setColor(QColor(0, 0, 255, 128)) for item in self.annotationList.selectedItems(): index = self.annotationList.row(item) mapTool = QgsMapTool(self.iface.mapCanvas()) point = mapTool.toCanvasCoordinates( self.annotationManager.annotations()[index].mapPosition()) pt1 = mapTool.toMapCoordinates( QPoint(point.x() - 10, point.y() - 10)) pt2 = mapTool.toMapCoordinates( QPoint(point.x() + 10, point.y() + 10)) rect = QgsRectangle(pt1, pt2) poly = QgsGeometry().fromRect(rect) self.rb.addGeometry(poly, None) def showAll(self): count = self.annotationList.count() for i in range(count): self.annotationList.item(i).setCheckState(Qt.Checked) def hideAll(self): count = self.annotationList.count() for i in range(count): self.annotationList.item(i).setCheckState(Qt.Unchecked) def showAllSelected(self): for item in self.annotationList.selectedItems(): item.setCheckState(Qt.Checked) def hideAllSelected(self): for item in self.annotationList.selectedItems(): item.setCheckState(Qt.Unchecked) def unload(self): del self.dock def tr(self, message): return QCoreApplication.translate('AnnotationManager', message) def refreshAnnotationTitle(self, annotation=None): if annotation is None: annotation = self.project.annotationManager().sender() item = self.annotationList.item( self.annotationManager.annotations().index(annotation)) title = 'Annotation' if isinstance(annotation, QgsTextAnnotation): title = annotation.document().toPlainText().split('\n')[0] if len(title) > 40: title = title[:40] + '(...)' item.setText(title) def refreshAnnotations(self): self.annotationList.clearSelection() self.annotationList.clear() for annotation in self.annotationManager.annotations(): item = QListWidgetItem() annotation.appearanceChanged.connect(self.refreshAnnotationTitle) if annotation.isVisible(): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) item.setFlags(item.flags()) self.annotationList.addItem(item) self.refreshAnnotationTitle(annotation) def removeAnnotation(self): if len(self.annotationList.selectedItems()) > 0: self.annotationManager.annotationRemoved.disconnect() trash = [] for item in self.annotationList.selectedItems(): index = self.annotationList.row(item) trash.append(self.annotationManager.annotations()[index]) while trash: self.annotationManager.removeAnnotation(trash.pop()) self.refreshAnnotations() self.annotationManager.annotationRemoved.connect( self.refreshAnnotations) def projectOpen(self): self.refreshAnnotations() def initGui(self): self.refreshAnnotations()
class SelectFeatureMapTool(QgsMapTool): def __init__(self, plugin): QgsMapTool.__init__(self, plugin.map_canvas) self.plugin = plugin self.doubleclick = False self.__pluginUnloaded = False settings = QSettings() qgsLineWidth = 2 # use fixed width qgsLineRed = settings.value("/qgis/digitizing/line_color_red", 255, type=int) qgsLineGreen = settings.value("/qgis/digitizing/line_color_green", 0, type=int) qgsLineBlue = settings.value("/qgis/digitizing/line_color_blue", 0, type=int) self.rubBandPol = QgsRubberBand(plugin.map_canvas, QGis.Line) self.rubBandPol.setColor(QColor(qgsLineRed, qgsLineGreen, qgsLineBlue)) self.rubBandPol.setWidth(qgsLineWidth) def activate(self): super(SelectFeatureMapTool, self).activate() self.plugin.iface.mainWindow().statusBar().showMessage(self.tr("Click on a parcel!")) self.currentDialog = None self.dialogPosition = None self.currentPossessionType = "INDIVIDUAL" # self.plugin.mapCanvas.setCursor(QCursor(Qt.ArrowCursor)) def canvasDoubleClickEvent(self, event): self.doubleclick = True def canvasReleaseEvent(self, event): if self.doubleclick: self.doubleclick = False return if event.button() <> Qt.LeftButton: return layer = self.plugin.getLayerByTableName('ca_parcel') if not layer: return # find out map coordinates from mouse click mapPoint = self.toLayerCoordinates(layer, event.pos()) tolerance = self.plugin.getTolerance(layer) area = QgsRectangle(mapPoint.x() - tolerance, mapPoint.y() - tolerance, mapPoint.x() + tolerance, mapPoint.y() + tolerance) request = QgsFeatureRequest() request.setFilterRect(area).setFlags(QgsFeatureRequest.ExactIntersect) request.setSubsetOfAttributes([0]) result = False for feature in layer.getFeatures(request): self.rubBandPol.reset(True) self.rubBandPol.addGeometry(feature.geometry(), layer) parcel_no = int(feature[0]) possession_type = self.__possessionType(parcel_no) if not self.currentDialog or possession_type != self.currentPossessionType: if self.currentDialog: self.dialogPosition = self.currentDialog.pos() self.currentDialog.reject() # if possession_type == "INDIVIDUAL": # self.currentDialog = PossessionDetailsDialog(self.plugin, self.plugin.iface.mainWindow()) # else: # self.currentDialog = CooperativePossessionDetailsDialog(self.plugin, self.plugin.iface.mainWindow()) self.currentPossessionType = possession_type self.connect(self.currentDialog, SIGNAL("rejected()"), self.__dialogClosed) if self.dialogPosition: self.currentDialog.move(self.dialogPosition) self.currentDialog.setParcelNo(parcel_no) if self.currentDialog.isHidden(): self.currentDialog.show() result = True break if not result: self.__resetTool() def __resetTool(self): if self.currentDialog: self.currentDialog.hide() self.dialogPosition = self.currentDialog.pos() self.rubBandPol.reset(True) def deactivate(self): if self.__pluginUnloaded: return self.plugin.iface.mainWindow().statusBar().showMessage("") self.doubleclick = False self.rubBandPol.reset(True) if self.currentDialog: self.currentDialog.reject() super(SelectFeatureMapTool, self).deactivate() def __dialogClosed(self): self.currentDialog = None #self.dialogPosition = None def setPluginUnloaded(self): self.__pluginUnloaded = True
class AdvancedIntersectionMapTool(QgsMapTool): def __init__(self, iface): self.iface = iface self.mapCanvas = iface.mapCanvas() QgsMapTool.__init__(self, self.mapCanvas) self.settings = MySettings() self.rubber = QgsRubberBand(self.mapCanvas) self.tolerance = self.settings.value("selectTolerance") units = self.settings.value("selectUnits") if units == "pixels": self.tolerance *= self.mapCanvas.mapUnitsPerPixel() def activate(self): QgsMapTool.activate(self) self.rubber.setWidth(self.settings.value("rubberWidth")) self.rubber.setColor(self.settings.value("rubberColor")) line_layer = MemoryLayers(self.iface).line_layer() # unset this tool if the layer is removed line_layer.layerDeleted.connect(self.unsetMapTool) self.layerId = line_layer.id() # create snapper for this layer self.snapLayer = QgsSnapper.SnapLayer() self.snapLayer.mLayer = line_layer self.snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment self.snapLayer.mTolerance = self.settings.value("selectTolerance") if self.settings.value("selectUnits") == "map": self.snapLayer.mUnitType = QgsTolerance.MapUnits else: self.snapLayer.mUnitType = QgsTolerance.Pixels def unsetMapTool(self): self.mapCanvas.unsetMapTool(self) def deactivate(self): self.rubber.reset() line_layer = QgsMapLayerRegistry.instance().mapLayer(self.layerId) if line_layer is not None: line_layer.layerDeleted.disconnect(self.unsetMapTool) QgsMapTool.deactivate(self) def canvasMoveEvent(self, mouseEvent): # put the observations within tolerance in the rubber band self.rubber.reset() for f in self.getFeatures(mouseEvent.pos()): self.rubber.addGeometry(f.geometry(), None) def canvasPressEvent(self, mouseEvent): pos = mouseEvent.pos() observations = self.getFeatures(pos) point = self.toMapCoordinates(pos) self.doIntersection(point, observations) def getFeatures(self, pixPoint): snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers([self.snapLayer]) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) # output snapped features features = [] alreadyGot = [] for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if featureId not in alreadyGot: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is not False: features.append(QgsFeature(f)) alreadyGot.append(featureId) return features def doIntersection(self, initPoint, observations): nObs = len(observations) if nObs < 2: return self.rubber.reset() self.dlg = IntersectionDialog(self.iface, observations, initPoint) if not self.dlg.exec_() or self.dlg.solution is None: return intersectedPoint = self.dlg.solution self.saveIntersectionResult(self.dlg.report, intersectedPoint) self.saveDimension(intersectedPoint, self.dlg.observations) def saveIntersectionResult(self, report, intersectedPoint): # save the intersection result (point) and its report # check first while True: if not self.settings.value("advancedIntersectionWritePoint"): break # if we do not place any point, skip layerid = self.settings.value("advancedIntersectionLayer") message = QCoreApplication.translate("IntersectIt", "To place the intersection solution," " you must select a layer in the settings.") status, intLayer = self.checkLayerExists(layerid, message) if status == 2: continue if status == 3: return if self.settings.value("advancedIntersectionWriteReport"): reportField = self.settings.value("reportField") message = QCoreApplication.translate("IntersectIt", "To save the intersection report, please select a field for it.") status = self.checkFieldExists(intLayer, reportField, message) if status == 2: continue if status == 3: return break # save the intersection results if self.settings.value("advancedIntersectionWritePoint"): f = QgsFeature() f.setGeometry(QgsGeometry().fromPoint(intersectedPoint)) if self.settings.value("advancedIntersectionWriteReport"): irep = intLayer.dataProvider().fieldNameIndex(reportField) f.addAttribute(irep, report) intLayer.dataProvider().addFeatures([f]) intLayer.updateExtents() self.mapCanvas.refresh() def saveDimension(self, intersectedPoint, observations): # check that dimension layer and fields have been set correctly if not self.settings.value("dimensionDistanceWrite") and not self.settings.value("dimensionOrientationWrite"): return # if we do not place any dimension, skip obsTypes = ("Distance", "Orientation") recheck = True while recheck: # settings might change during checking, # so recheck both observation types whenever the settings dialog is shown recheck = False for obsType in obsTypes: while True: if not self.settings.value("dimension"+obsType+"Write"): break # check layer layerId = self.settings.value("dimension"+obsType+"Layer") message = QCoreApplication.translate("IntersectIt", "To place dimensions, " "you must define a layer in the settings.") status, dimLayer = self.checkLayerExists(layerId, message) if status == 2: recheck = True continue if status == 3: return # check fields if self.settings.value("dimension"+obsType+"ObservationWrite"): obsField = self.settings.value("dimension"+obsType+"ObservationField") message = QCoreApplication.translate("IntersectIt", "To save the observation in the layer," " please select a field for it.") status = self.checkFieldExists(dimLayer, obsField, message) if status == 2: recheck = True continue if status == 3: return if self.settings.value("dimension"+obsType+"PrecisionWrite"): precisionField = self.settings.value("dimension"+obsType+"PrecisionField") message = QCoreApplication.translate("IntersectIt", "To save the precision of observation," " please select a field for it.") status = self.checkFieldExists(dimLayer, precisionField, message) if status == 2: recheck = True continue if status == 3: return break # save the intersection results for obsType in obsTypes: if self.settings.value("dimension"+obsType+"Write"): layerid = self.settings.value("dimension"+obsType+"Layer") layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if layer is None: continue initFields = layer.dataProvider().fields() features = [] for obs in observations: if obs["type"] != obsType.lower(): continue f = QgsFeature() f.setFields(initFields) f.initAttributes(initFields.size()) if self.settings.value("dimension"+obsType+"ObservationWrite"): f[self.settings.value("dimension"+obsType+"ObservationField")] = obs["observation"] if self.settings.value("dimension"+obsType+"PrecisionWrite"): f[self.settings.value("dimension"+obsType+"PrecisionField")] = obs["precision"] p0 = QgsPoint(obs["x"], obs["y"]) p1 = intersectedPoint if obs["type"] == "distance": geom = Arc(p0, p1).geometry() elif obs["type"] == "orientation": geom = QgsGeometry().fromPolyline([p0, p1]) else: raise NameError("Invalid observation %s" % obs["type"]) f.setGeometry(geom) features.append(QgsFeature(f)) if not layer.dataProvider().addFeatures(features): self.iface.messageBar().pushMessage("Could not commit %s observations" % obsType, QgsMessageBar.CRITICAL) layer.updateExtents() self.mapCanvas.refresh() def checkLayerExists(self, layerid, message): # returns: # 1: layer exists # 2: does not exist, settings has been open, so loop once more (i.e. continue) # 3: does not exist, settings not edited, so cancel layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if layer is not None: return 1, layer reply = QMessageBox.question(self.iface.mainWindow(), "Intersect It", message + " Would you like to open settings?", QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if MySettingsDialog().exec_(): return 2 return 3 def checkFieldExists(self, layer, field, message): # returns: # 1: field exists # 2: does not exist, settings has been open, so loop once more (i.e. continue) # 3: does not exist, settings not edited, so cancel if layer.dataProvider().fieldNameIndex(field) != -1: return 1 reply = QMessageBox.question(self.iface.mainWindow(), "Intersect It", message + " Would you like to open settings?", QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if MySettingsDialog().exec_(): return 2 return 3
class ReverseGeocodeTool(QgsMapTool): def __init__(self, iface, settings): self.canvas = iface.mapCanvas() QgsMapTool.__init__(self, self.canvas) self.iface = iface self.settings = settings self.reverseGeoCodeDialog = ReverseGeocodeDialog( self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.TopDockWidgetArea, self.reverseGeoCodeDialog) self.reverseGeoCodeDialog.hide() self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326') self.marker = None # Set up a polygon/line rubber band self.rubber = QgsRubberBand(self.canvas) self.rubber.setColor(QColor(255, 70, 0, 200)) self.rubber.setWidth(5) self.rubber.setBrushStyle(Qt.NoBrush) def activate(self): '''When activated set the cursor to a crosshair.''' self.canvas.setCursor(Qt.CrossCursor) self.show() def unload(self): self.iface.removeDockWidget(self.reverseGeoCodeDialog) self.reverseGeoCodeDialog = None if self.rubber: self.canvas.scene().removeItem(self.rubber) del self.rubber self.removeMarker() def addMarker(self, lat, lon): if self.marker: self.removeMarker() canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(self.epsg4326, canvasCrs, QgsProject.instance()) center = transform.transform(lon, lat) self.marker = QgsVertexMarker(self.canvas) self.marker.setCenter(center) self.marker.setColor(QColor(255, 70, 0)) self.marker.setIconSize(15) self.marker.setIconType(QgsVertexMarker.ICON_X) self.marker.setPenWidth(3) self.marker.show() def removeMarker(self): if self.marker: self.canvas.scene().removeItem(self.marker) self.marker = None def clearSelection(self): self.removeMarker() self.rubber.reset() def transform_geom(self, geometry): canvasCrs = self.canvas.mapSettings().destinationCrs() geom = QgsGeometry(geometry) geom.transform( QgsCoordinateTransform(self.epsg4326, canvasCrs, QgsProject.instance())) return geom def show(self): self.reverseGeoCodeDialog.show() def request(self, url): fetcher = QgsNetworkContentFetcher() fetcher.fetchContent(QUrl(url)) evloop = QEventLoop() fetcher.finished.connect(evloop.quit) evloop.exec_(QEventLoop.ExcludeUserInputEvents) fetcher.finished.disconnect(evloop.quit) return fetcher.contentAsString() def canvasReleaseEvent(self, event): # Make sure the point is transfored to 4326 self.clearSelection() pt = self.toMapCoordinates(event.pos()) canvasCRS = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(canvasCRS, self.epsg4326, QgsProject.instance()) pt = transform.transform(pt.x(), pt.y()) url = '{}?format=json&lat={:f}&lon={:f}&zoom={:d}&addressdetails=0&polygon_text=1'.format( self.settings.reverseURL(), pt.y(), pt.x(), self.settings.levelOfDetail) # print( url ) jsondata = self.request(url) try: jd = json.loads(jsondata) try: display_name = jd['display_name'] self.setText(display_name) except KeyError: self.setText("[Could not find address]") try: wkt = jd['geotext'] geometry = QgsGeometry.fromWkt(wkt) if geometry.wkbType() == QgsWkbTypes.Point: pt = geometry.asPoint() lon = pt.x() lat = pt.y() self.addMarker(lat, lon) else: geometry = self.transform_geom(geometry) self.rubber.addGeometry(geometry, None) self.rubber.show() except KeyError: try: lon = float(jd['lon']) lat = float(jd['lat']) self.addMarker(lat, lon) except: pass except Exception: self.setText("Error: " + jsondata) if not self.reverseGeoCodeDialog.isVisible(): self.show() def setText(self, text): self.reverseGeoCodeDialog.addressLineEdit.setText(text)
class MoveTool(QgsMapToolAdvancedDigitizing): """ Map tool class to move or copy an object """ def __init__(self, iface): """ Constructor :param iface: interface """ QgsMapToolAdvancedDigitizing.__init__(self, iface.mapCanvas(), iface.cadDockWidget()) self.__iface = iface self.icon_path = ':/plugins/VDLTools/icons/move_icon.png' self.text = QCoreApplication.translate("VDLTools", "Move/Copy a feature") self.setCursor(Qt.ArrowCursor) self.__isEditing = False self.__findVertex = False self.__onMove = False self.__layer = None self.__confDlg = None self.__lastFeatureId = None self.__selectedFeature = None self.__rubberBand = None self.__rubberSnap = None self.__newFeature = None self.__selectedVertex = None def activate(self): """ When the action is selected """ QgsMapToolAdvancedDigitizing.activate(self) if self.__layer.geometryType() == QGis.Point: self.setMode(self.CaptureLine) else: self.setMode(self.CaptureNone) def deactivate(self): """ When the action is deselected """ self.__cancel() QgsMapToolAdvancedDigitizing.deactivate(self) def toolName(self): """ To get the tool name :return: tool name """ return QCoreApplication.translate("VDLTools", "Move/Copy") def startEditing(self): """ To set the action as enable, as the layer is editable """ self.action().setEnabled(True) Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer.editingStopped.connect(self.stopEditing) def stopEditing(self): """ To set the action as disable, as the layer is not editable """ self.action().setEnabled(False) Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) self.__layer.editingStarted.connect(self.startEditing) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() def setTool(self): """ To set the current tool as this one """ self.canvas().setMapTool(self) def __cancel(self): """ To cancel used variables """ if self.__rubberBand is not None: self.canvas().scene().removeItem(self.__rubberBand) self.__rubberBand.reset() self.__rubberBand = None if self.__rubberSnap is not None: self.canvas().scene().removeItem(self.__rubberSnap) self.__rubberSnap.reset() self.__rubberSnap = None self.__isEditing = False self.__findVertex = False self.__onMove = False self.__lastFeatureId = None self.__selectedFeature = None self.__confDlg = None self.__newFeature = None self.__selectedVertex = None self.__layer.removeSelection() if self.__layer.geometryType() == QGis.Point: self.setMode(self.CaptureLine) else: self.setMode(self.CaptureNone) def __removeLayer(self): """ To remove the current working layer """ if self.__layer is not None: if self.__layer.isEditable(): Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) else: Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer = None def setEnable(self, layer): """ To check if we can enable the action for the selected layer :param layer: selected layer """ if layer is not None and layer.type() == QgsMapLayer.VectorLayer: if layer == self.__layer: return if self.__layer is not None: if self.__layer.isEditable(): Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) else: Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer = layer if self.__layer.geometryType() == QGis.Point: self.setMode(self.CaptureLine) else: self.setMode(self.CaptureNone) if self.__layer.isEditable(): self.action().setEnabled(True) self.__layer.editingStopped.connect(self.stopEditing) else: self.action().setEnabled(False) self.__layer.editingStarted.connect(self.startEditing) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() return self.action().setEnabled(False) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() self.__removeLayer() def __pointPreview(self, point): """ To create a point geometry preview (rubberBand) :param point: new position as mapPoint """ point_v2 = GeometryV2.asPointV2(self.__selectedFeature.geometry(), self.__iface) self.__newFeature = QgsPointV2(point.x(), point.y()) self.__newFeature.addZValue(point_v2.z()) self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point) self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.clone()), None) def __linePreview(self, point): """ To create a line geometry preview (rubberBand) :param point: new position as mapPoint """ line_v2, curved = GeometryV2.asLineV2( self.__selectedFeature.geometry(), self.__iface) vertex = QgsPointV2() line_v2.pointAt(self.__selectedVertex, vertex) self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line) dx = vertex.x() - point.x() dy = vertex.y() - point.y() if isinstance(curved, (list, tuple)): self.__newFeature = QgsCompoundCurveV2() for pos in range(line_v2.nCurves()): curve_v2 = self.__newCurve(curved[pos], line_v2.curveAt(pos), dx, dy) self.__newFeature.addCurve(curve_v2) if pos == 0: self.__rubberBand.setToGeometry( QgsGeometry(curve_v2.curveToLine()), None) else: self.__rubberBand.addGeometry( QgsGeometry(curve_v2.curveToLine()), None) else: self.__newFeature = self.__newCurve(curved, line_v2, dx, dy) self.__rubberBand.setToGeometry( QgsGeometry(self.__newFeature.curveToLine()), None) @staticmethod def __newCurve(curved, line_v2, dx, dy): """ To create a new moved line :param curved: if the line is curved :param line_v2: the original line :param dx: x translation :param dy: y translation :return: the new line """ if curved: newCurve = QgsCircularStringV2() else: newCurve = QgsLineStringV2() points = [] for pos in range(line_v2.numPoints()): x = line_v2.pointN(pos).x() - dx y = line_v2.pointN(pos).y() - dy pt = QgsPointV2(x, y) pt.addZValue(line_v2.pointN(pos).z()) points.append(pt) newCurve.setPoints(points) return newCurve def __polygonPreview(self, point): """ To create a polygon geometry preview (rubberBand) :param point: new position as mapPoint """ polygon_v2, curved = GeometryV2.asPolygonV2( self.__selectedFeature.geometry(), self.__iface) vertex = polygon_v2.vertexAt( GeometryV2.polygonVertexId(polygon_v2, self.__selectedVertex)) dx = vertex.x() - point.x() dy = vertex.y() - point.y() self.__newFeature = QgsCurvePolygonV2() self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line) line_v2 = self.__newCurve(curved[0], polygon_v2.exteriorRing(), dx, dy) self.__newFeature.setExteriorRing(line_v2) self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()), None) for num in range(polygon_v2.numInteriorRings()): line_v2 = self.__newCurve(curved[num + 1], polygon_v2.interiorRing(num), dx, dy) self.__newFeature.addInteriorRing(line_v2) self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()), None) def __onConfirmCancel(self): """ When the Cancel button in Move Confirm Dialog is pushed """ self.__confDlg.reject() def __onConfirmMove(self): """ When the Move button in Move Confirm Dialog is pushed """ geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage(QCoreApplication.translate( "VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0) self.__layer.changeGeometry(self.__selectedFeature.id(), geometry) self.__confDlg.accept() self.__cancel() def __onConfirmCopy(self): """ When the Copy button in Move Confirm Dialog is pushed """ geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage(QCoreApplication.translate( "VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0) feature = QgsFeature(self.__layer.pendingFields()) feature.setGeometry(geometry) primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn() for field in self.__selectedFeature.fields(): if field.name() != primaryKey: feature.setAttribute( field.name(), self.__selectedFeature.attribute(field.name())) if len(self.__selectedFeature.fields()) > 0 and self.__layer.editFormConfig().suppress() != \ QgsEditFormConfig.SuppressOn: self.__iface.openFeatureForm(self.__layer, feature) else: self.__layer.addFeature(feature) self.__confDlg.accept() self.__cancel() def keyReleaseEvent(self, event): """ When keyboard is pressed :param event: keyboard event """ if event.key() == Qt.Key_Escape: self.__cancel() def cadCanvasMoveEvent(self, event): """ When the mouse is moved :param event: mouse event """ if type(event) == QMoveEvent: map_point = self.toMapCoordinates(event.pos()) else: map_point = event.mapPoint() if not self.__isEditing and not self.__findVertex and not self.__onMove: laySettings = QgsSnappingUtils.LayerConfig(self.__layer, QgsPointLocator.All, 10, QgsTolerance.Pixels) f_l = Finder.findClosestFeatureAt(map_point, self.canvas(), [laySettings]) if f_l is not None and self.__lastFeatureId != f_l[0].id(): self.__lastFeatureId = f_l[0].id() self.__layer.setSelectedFeatures([f_l[0].id()]) if f_l is None: self.__layer.removeSelection() self.__lastFeatureId = None elif self.__findVertex: if self.__rubberBand is not None: self.__rubberBand.reset() closest = self.__selectedFeature.geometry().closestVertex( map_point) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) self.__rubberBand.setToGeometry( QgsGeometry().fromPoint(closest[0]), None) elif self.__onMove: if self.__rubberBand is not None: self.__rubberBand.reset() if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(map_point) elif self.__layer.geometryType() == QGis.Line: self.__linePreview(map_point) else: self.__pointPreview(map_point) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) self.__rubberBand.setWidth(2) if self.__layer.geometryType() != QGis.Point: self.__rubberBand.setLineStyle(Qt.DotLine) else: self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(8) if self.__rubberSnap is not None: self.__rubberSnap.reset() else: self.__rubberSnap = QgsRubberBand(self.canvas(), QGis.Point) self.__rubberSnap.setColor(color) self.__rubberSnap.setWidth(2) self.__rubberSnap.setIconSize(20) match = Finder.snap(map_point, self.canvas()) if match.hasVertex() or match.hasEdge(): point = match.point() if match.hasVertex(): if match.layer(): self.__rubberSnap.setIcon(4) else: self.__rubberSnap.setIcon(1) if match.hasEdge(): intersection = Finder.snapCurvedIntersections( point, self.canvas(), self) if intersection is not None: self.__rubberSnap.setIcon(1) point = intersection else: self.__rubberSnap.setIcon(3) self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(point), None) def cadCanvasReleaseEvent(self, event): """ When the mouse is clicked :param event: mouse event """ if not self.__isEditing and not self.__findVertex and not self.__onMove: found_features = self.__layer.selectedFeatures() if len(found_features) > 0: if len(found_features) > 1: self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "One feature at a time"), level=QgsMessageBar.INFO) return self.__selectedFeature = found_features[0] if self.__layer.geometryType() != QGis.Point: self.__iface.messageBar().pushMessage( QCoreApplication.translate( "VDLTools", "Select vertex for moving (ESC to undo)"), level=QgsMessageBar.INFO, duration=3) self.__findVertex = True self.setMode(self.CaptureLine) self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point) else: self.setMode(self.CaptureNone) self.__onMove = True elif self.__findVertex: self.__findVertex = False self.setMode(self.CaptureNone) closest = self.__selectedFeature.geometry().closestVertex( event.mapPoint()) self.__selectedVertex = closest[1] self.__onMove = True elif self.__onMove: self.__onMove = False mapPoint = event.mapPoint() match = Finder.snap(event.mapPoint(), self.canvas()) if match.hasVertex() or match.hasEdge(): mapPoint = match.point() if match.hasEdge(): intersection = Finder.snapCurvedIntersections( mapPoint, self.canvas(), self) if intersection is not None: mapPoint = intersection self.__isEditing = True if self.__rubberBand is not None: self.__rubberBand.reset() if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(mapPoint) elif self.__layer.geometryType() == QGis.Line: self.__linePreview(mapPoint) else: self.__pointPreview(mapPoint) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) if self.__layer.geometryType() != QGis.Point: self.__rubberBand.setWidth(2) self.__rubberBand.setLineStyle(Qt.DotLine) else: self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) self.__confDlg = MoveConfirmDialog() self.__confDlg.rejected.connect(self.__cancel) self.__confDlg.moveButton().clicked.connect(self.__onConfirmMove) self.__confDlg.copyButton().clicked.connect(self.__onConfirmCopy) self.__confDlg.cancelButton().clicked.connect( self.__onConfirmCancel) self.__confDlg.show()
class EditTool(MapTool): """ Inspection tool which copies the feature to a new layer and copies selected data from the underlying feature. """ finished = pyqtSignal(QgsVectorLayer, QgsFeature) def __init__(self, canvas, layers, snapradius=2): MapTool.__init__(self, canvas, layers) self.canvas = canvas self.radius = snapradius self.band = QgsRubberBand(self.canvas) self.band.setColor(QColor.fromRgb(224, 162, 16)) self.band.setWidth(3) self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #FF0000", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) def getFeatures(self, point): searchRadius = (QgsTolerance.toleranceInMapUnits( self.radius, self.layers[0], self.canvas.mapRenderer(), QgsTolerance.Pixels)) point = self.toMapCoordinates(point) rect = QgsRectangle() rect.setXMinimum(point.x() - searchRadius) rect.setXMaximum(point.x() + searchRadius) rect.setYMinimum(point.y() - searchRadius) rect.setYMaximum(point.y() + searchRadius) rq = QgsFeatureRequest().setFilterRect(rect) self.band.reset() for layer in self.layers: rq = QgsFeatureRequest().setFilterRect(rect) for feature in layer.getFeatures(rq): if feature.isValid(): yield feature, layer def canvasMoveEvent(self, event): for feature, _ in self.getFeatures(point=event.pos()): self.band.addGeometry(feature.geometry(), None) def canvasReleaseEvent(self, event): features = list(self.getFeatures(point=event.pos())) if len(features) == 1: feature = features[0] self.finished.emit(feature[1], feature[0]) elif len(features) > 0: listUi = ListFeaturesForm() listUi.loadFeatureList(features) listUi.openFeatureForm.connect(self.finished) listUi.exec_() 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.canvas.setCursor(self.cursor) def deactivate(self): """ Deactive the tool. """ pass def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True
class SoLocatorFilter(QgsLocatorFilter): HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS SoLocator Filter'} message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget) def __init__(self, iface: QgisInterface = None): """" :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise """ super().__init__() self.iface = iface self.settings = Settings() # following properties will only be used in main thread self.rubber_band = None self.map_canvas: QgsMapCanvas = None self.transform_ch = None self.current_timer = None self.result_found = False self.nam_fetch_feature = None if iface is not None: # happens only in main thread self.map_canvas = iface.mapCanvas() self.map_canvas.destinationCrsChanged.connect( self.create_transforms) self.rubber_band = QgsRubberBand(self.map_canvas, QgsWkbTypes.PolygonGeometry) self.rubber_band.setColor(QColor(255, 50, 50, 200)) self.rubber_band.setFillColor(QColor(255, 255, 50, 160)) self.rubber_band.setBrushStyle(Qt.SolidPattern) self.rubber_band.setLineStyle(Qt.SolidLine) self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE) self.rubber_band.setIconSize(15) self.rubber_band.setWidth(4) self.rubber_band.setBrushStyle(Qt.NoBrush) self.create_transforms() def name(self): return 'SoLocator' def clone(self): return SoLocatorFilter() def priority(self): return QgsLocatorFilter.Highest def displayName(self): return 'SoLocator' def prefix(self): return 'sol' def clearPreviousResults(self): if self.rubber_band: self.rubber_band.reset(QgsWkbTypes.PointGeometry) if self.current_timer is not None: self.current_timer.stop() self.current_timer.deleteLater() self.current_timer = None def hasConfigWidget(self): return True def openConfigWidget(self, parent=None): dlg = ConfigDialog(parent) dlg.exec_() def create_transforms(self): # this should happen in the main thread src_crs_ch = QgsCoordinateReferenceSystem('EPSG:2056') assert src_crs_ch.isValid() dst_crs = self.map_canvas.mapSettings().destinationCrs() self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs, QgsProject.instance()) def enabled_dataproducts(self): categories = DATA_PRODUCTS.keys() skipped = self.settings.value('skipped_dataproducts') return ','.join(list(filter(lambda id: id not in skipped, categories))) @staticmethod def url_with_param(url, params) -> str: url = QUrl(url) q = QUrlQuery(url) for key, value in params.items(): q.addQueryItem(key, value) url.setQuery(q) return url.url() def fetchResults(self, search: str, context: QgsLocatorContext, feedback: QgsFeedback): try: self.dbg_info("start solocator search...") if len(search) < 3: return self.result_found = False params = { 'searchtext': str(search), 'filter': self.enabled_dataproducts(), 'limit': str(self.settings.value('results_limit')) } nam = NetworkAccessManager() feedback.canceled.connect(nam.abort) url = self.url_with_param(SEARCH_URL, params) self.dbg_info(url) try: (response, content) = nam.request(url, headers=self.HEADERS, blocking=True) self.handle_response(response, search) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(err, Qgis.Info) if not self.result_found: result = QgsLocatorResult() result.filter = self result.displayString = self.tr('No result found.') result.userData = NoResult self.resultFetched.emit(result) except Exception as e: self.info(e, Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] self.info( '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) self.info( traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def data_product_qgsresult(self, data: dict, sub_layer: bool, score: float, stacktype) -> QgsLocatorResult: result = QgsLocatorResult() result.filter = self result.displayString = '{prefix}{title}'.format( prefix=' ↳ ' if sub_layer else '', title=data['display']) if stacktype == 'background': result.group = 'Hintergrundkarten' else: loading_mode: LoadingMode = self.settings.value( 'default_layer_loading_mode') result.group = 'Vordergrundkarten (Doppelklick: {normal}, Ctrl-Doppelklick: {alt})'.format( normal=loading_mode, alt=loading_mode.alternate_mode()) result.userData = DataProductResult( type=data['type'], dataproduct_id=data['dataproduct_id'], display=data['display'], dset_info=data['dset_info'], stacktype=stacktype, sublayers=data.get('sublayers', None)) data_product = 'dataproduct' data_type = data['type'] result.icon, result.description = dataproduct2icon_description( data_product, data_type) result.score = score return result def handle_response(self, response, search_text: str): try: if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info("Error in main response with status code: " "{} from {}".format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) # Since results are ordered by score (0 to 1) # we use an ordering score to keep the same order than the one from the remote service score = 1 # sub-filtering # dbg_info(data['result_counts']) if len(data['result_counts']) > 1: for _filter in data['result_counts']: result = QgsLocatorResult() result.filter = self result.group = 'Suche verfeinern' result.displayString = _filter['filterword'] if _filter['count']: result.displayString += ' ({})'.format( _filter['count']) self.dbg_info(_filter) result.icon, _ = dataproduct2icon_description( _filter['dataproduct_id'], 'datasetview') result.userData = FilterResult(_filter['filterword'], search_text) result.score = score self.resultFetched.emit(result) score -= 0.001 for res in data['results']: # dbg_info(res) result = QgsLocatorResult() result.filter = self if 'feature' in res.keys(): f = res['feature'] # dbg_info("feature: {}".format(f)) result.displayString = f['display'] result.group = 'Orte' result.userData = FeatureResult( dataproduct_id=f['dataproduct_id'], id_field_name=f['id_field_name'], id_field_type=f['id_field_type'], feature_id=f['feature_id']) data_product = f['dataproduct_id'] data_type = None result.icon, result.description = dataproduct2icon_description( data_product, data_type) result.score = score self.resultFetched.emit(result) score -= 0.001 elif 'dataproduct' in res.keys(): dp = res['dataproduct'] # self.dbg_info("data_product: {}".format(dp)) result = self.data_product_qgsresult( dp, False, score, dp['stacktype']) self.resultFetched.emit(result) score -= 0.001 # also give sublayers for layer in dp.get('sublayers', []): always_show_sublayers = True if always_show_sublayers or search_text.lower( ) in layer['display'].lower(): result = self.data_product_qgsresult( layer, True, score, dp['stacktype']) self.resultFetched.emit(result) score -= 0.001 else: continue self.result_found = True except Exception as e: self.info(str(e), Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] self.info( '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) self.info( traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def triggerResult(self, result: QgsLocatorResult): # this is run in the main thread, i.e. map_canvas is not None self.clearPreviousResults() ctrl_clicked = Qt.ControlModifier == QApplication.instance( ).queryKeyboardModifiers() self.dbg_info(("CTRL pressed: {}".format(ctrl_clicked))) user_data = self.get_user_data(result) if type(user_data) == NoResult: pass elif type(user_data) == FilterResult: self.filtered_search(user_data) elif type(user_data) == FeatureResult: self.fetch_feature(user_data) elif type(user_data) == DataProductResult: self.fetch_data_product(user_data, ctrl_clicked) else: self.info('Incorrect result. Please contact support', Qgis.Critical) def filtered_search(self, filter_result: FilterResult): search_text = '{prefix} {filter_word}: {search}'.format( prefix=self.activePrefix(), filter_word=filter_result.filter_word, search=filter_result.search) # Compatibility for QGIS < 3.10 # TODO: remove try: self.iface.locatorSearch(search_text) except AttributeError: for w in self.iface.mainWindow().findChildren(QgsFilterLineEdit): if hasattr(w.parent(), 'search') and hasattr( w.parent(), 'invalidateResults'): w.setText(search_text) w.parent().setFocus(True) return raise NameError('Locator not found') def highlight(self, geometry: QgsGeometry): self.clearPreviousResults() if geometry is None: return self.rubber_band.reset(geometry.type()) self.rubber_band.addGeometry(geometry, None) rect = geometry.boundingBox() if not self.settings.value('keep_scale'): if rect.isEmpty(): current_extent = self.map_canvas.extent() rect = current_extent.scaled( self.settings.value('point_scale') / self.map_canvas.scale(), rect.center()) else: rect.scale(4) self.map_canvas.setExtent(rect) self.map_canvas.refresh() self.current_timer = QTimer() self.current_timer.timeout.connect(self.clearPreviousResults) self.current_timer.setSingleShot(True) self.current_timer.start(5000) def fetch_feature(self, feature: FeatureResult): self.dbg_info(feature) url = '{url}/{dataset}/{id}'.format(url=FEATURE_URL, dataset=feature.dataproduct_id, id=feature.feature_id) self.nam_fetch_feature = NetworkAccessManager() self.dbg_info(url) self.nam_fetch_feature.finished.connect(self.parse_feature_response) self.nam_fetch_feature.request(url, headers=self.HEADERS, blocking=False) def parse_feature_response(self, response): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info("Error in feature response with status code: " "{} from {}".format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) self.dbg_info(data.keys()) self.dbg_info(data['properties']) self.dbg_info(data['geometry']) self.dbg_info(data['crs']) self.dbg_info(data['type']) assert data['crs']['properties'][ 'name'] == 'urn:ogc:def:crs:EPSG::2056' geometry_type = data['geometry']['type'] geometry = QgsGeometry() if geometry_type.lower() == 'point': geometry = QgsGeometry.fromPointXY( QgsPointXY(data['geometry']['coordinates'][0], data['geometry']['coordinates'][1])) elif geometry_type.lower() == 'polygon': rings = data['geometry']['coordinates'] for r in range(0, len(rings)): for p in range(0, len(rings[r])): rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1]) geometry = QgsGeometry.fromPolygonXY(rings) elif geometry_type.lower() == 'multipolygon': islands = data['geometry']['coordinates'] for i in range(0, len(islands)): for r in range(0, len(islands[i])): for p in range(0, len(islands[i][r])): islands[i][r][p] = QgsPointXY(islands[i][r][p][0], islands[i][r][p][1]) geometry = QgsGeometry.fromMultiPolygonXY(islands) else: # SoLocator does not handle {geometry_type} yet. Please contact support self.info( 'SoLocator unterstützt den Geometrietyp {geometry_type} nicht.' ' Bitte kontaktieren Sie den Support.'.format( geometry_type=geometry_type), Qgis.Warning) geometry.transform(self.transform_ch) self.highlight(geometry) def fetch_data_product(self, product: DataProductResult, alternate_mode: bool): self.dbg_info(product) url = '{url}/{dataproduct_id}'.format( url=DATA_PRODUCT_URL, dataproduct_id=product.dataproduct_id) self.nam_fetch_feature = NetworkAccessManager() self.dbg_info(url) is_background = product.stacktype == 'background' self.dbg_info('is_background {}'.format(is_background)) self.nam_fetch_feature.finished.connect( lambda response: self.parse_data_product_response( response, is_background, alternate_mode)) self.nam_fetch_feature.request(url, headers=self.HEADERS, blocking=False) def parse_data_product_response(self, response, is_background: bool, alternate_mode: bool): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info("Error in feature response with status code: " "{} from {}".format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) LayerLoader(data, self.iface, is_background, alternate_mode) def info(self, msg="", level=Qgis.Info): self.logMessage(str(msg), level) def dbg_info(self, msg=""): if DEBUG: self.info(msg) def get_user_data(self, result): if hasattr(result, 'getUserData'): return result.getUserData() else: return result.userData
class FinderBox(QComboBox): running = False toFinish = 0 searchStarted = pyqtSignal() searchFinished = pyqtSignal() def __init__(self, finders, iface, parent=None): self.iface = iface self.mapCanvas = iface.mapCanvas() self.rubber = QgsRubberBand(self.mapCanvas) self.rubber.setColor(QColor(255, 255, 50, 200)) self.rubber.setIcon(self.rubber.ICON_CIRCLE) self.rubber.setIconSize(15) self.rubber.setWidth(4) self.rubber.setBrushStyle(Qt.NoBrush) QComboBox.__init__(self, parent) self.setEditable(True) self.setInsertPolicy(QComboBox.InsertAtTop) self.setMinimumHeight(27) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.insertSeparator(0) self.lineEdit().returnPressed.connect(self.search) self.resultView = QTreeView() self.resultView.setHeaderHidden(True) self.resultView.setMinimumHeight(300) self.resultView.activated.connect(self.itemActivated) self.resultView.pressed.connect(self.itemPressed) self.setView(self.resultView) self.resultModel = ResultModel(self) self.setModel(self.resultModel) self.finders = finders for finder in self.finders.values(): finder.resultFound.connect(self.resultFound) finder.limitReached.connect(self.limitReached) finder.finished.connect(self.finished) finder.message.connect(self.message) def __del__(self): if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber def search(self): if self.running: return toFind = self.lineEdit().text() if not toFind or toFind == '': return self.running = True self.searchStarted.emit() self.resultModel.clearResults() self.resultModel.truncateHistory(MySettings().value("historyLength")) self.resultModel.setLoading(True) self.showPopup() QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) # create categories in special order and count activated ones for finder in self.finders.values(): if finder.activated(): self.resultModel.addResult(finder.name) bbox = self.mapCanvas.fullExtent() for finder in self.finders.values(): if finder.activated(): finder.start(toFind, bbox=bbox) def stop(self): for finder in self.finders.values(): if finder.isRunning(): finder.stop() def resultFound(self, finder, layername, value, geometry, srid): self.resultModel.addResult(finder.name, layername, value, geometry, srid) self.resultView.expandAll() def limitReached(self, finder, layername): self.resultModel.addEllipsys(finder.name, layername) def finished(self, finder): for finder in self.finders.values(): if finder.isRunning(): return self.running = False self.searchFinished.emit() self.resultModel.setLoading(False) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def message(self, finder, message, level): self.iface.messageBar().pushMessage("Quick Finder", message, level, 3) def itemActivated(self, index): item = self.resultModel.itemFromIndex(index) self.showItem(item) def itemPressed(self, index): item = self.resultModel.itemFromIndex(index) if QApplication.mouseButtons() == Qt.LeftButton: self.showItem(item) def showItem(self, item): if isinstance(item, ResultItem): self.resultModel.setSelected(item, self.resultView.palette()) geometry = self.transformGeom(item) self.rubber.reset(geometry.type()) self.rubber.setToGeometry(geometry, None) self.zoomToRubberBand() return if isinstance(item, GroupItem): child = item.child(0) if isinstance(child, ResultItem): self.resultModel.setSelected(item, self.resultView.palette()) self.rubber.reset(child.geometry.type()) for i in xrange(0, item.rowCount()): geometry = self.transformGeom(item.child(i)) self.rubber.addGeometry(geometry, None) self.zoomToRubberBand() return if item.__class__.__name__ == 'QStandardItem': self.resultModel.setSelected(None, self.resultView.palette()) self.rubber.reset() return def transformGeom(self, item): geometry = item.geometry src_crs = QgsCoordinateReferenceSystem() src_crs.createFromSrid(item.srid) dest_crs = self.mapCanvas.mapRenderer().destinationCrs() geom = item.geometry geom.transform( QgsCoordinateTransform(src_crs, dest_crs) ) return geom def zoomToRubberBand(self): geom = self.rubber.asGeometry() if geom: rect = geom.boundingBox() rect.scale(1.5) self.mapCanvas.setExtent(rect) self.mapCanvas.refresh()
class GeodesicMeasureDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent): super(GeodesicMeasureDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.canvas = iface.mapCanvas() settings = QSettings() self.restoreGeometry(settings.value("ShapeTools/MeasureDialogGeometry", QByteArray(), type=QByteArray)) self.closeButton.clicked.connect(self.closeDialog) self.newButton.clicked.connect(self.newDialog) self.unitsComboBox.addItems(UNITS) self.tableWidget.setColumnCount(3) self.tableWidget.setSortingEnabled(False) self.tableWidget.setHorizontalHeaderLabels(['Heading To', 'Heading From', 'Distance']) self.unitsComboBox.activated.connect(self.unitsChanged) self.capturedPoints = [] self.distances = [] self.geod = Geodesic.WGS84 self.activeMeasuring = True self.unitsChanged() self.currentDistance = 0.0 color = QColor(222, 167, 67, 150) self.pointRb = QgsRubberBand(self.canvas, QGis.Point) self.pointRb.setColor(color) self.pointRb.setIconSize(10) self.lineRb = QgsRubberBand(self.canvas, QGis.Line) self.lineRb.setColor(color) self.lineRb.setWidth(3) self.tempRb = QgsRubberBand(self.canvas, QGis.Line) self.tempRb.setColor(color) self.tempRb.setWidth(3) def ready(self): return self.activeMeasuring def stop(self): self.activeMeasuring = False def closeEvent(self, event): self.closeDialog() def closeDialog(self): self.clear() QSettings().setValue( "ShapeTools/MeasureDialogGeometry", self.saveGeometry()) self.close() def newDialog(self): self.clear() def unitsChanged(self): label = "Distance [{}]".format(UNITS[self.unitsComboBox.currentIndex()]) item = QTableWidgetItem(label) self.tableWidget.setHorizontalHeaderItem(2, item) ptcnt = len(self.capturedPoints) if ptcnt >= 2: i = 0 while i < ptcnt-1: item = QTableWidgetItem('{:.4f}'.format(self.unitDistance(self.distances[i]))) self.tableWidget.setItem(i, 2, item) i += 1 self.formatTotal() def motionReady(self): if len(self.capturedPoints) > 0 and self.activeMeasuring: return True return False def addPoint(self, pt, button): self.currentDistance = 0 index = len(self.capturedPoints) if index > 0 and pt == self.capturedPoints[index-1]: # the clicked point is the same as the previous so just ignore it return self.capturedPoints.append(pt) # Add rubber band points canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(epsg4326, canvasCrs) ptCanvas = transform.transform(pt.x(), pt.y()) self.pointRb.addPoint(ptCanvas, True) # If there is more than 1 point add it to the table if index > 0: (distance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index-1], self.capturedPoints[index]) self.distances.append(distance) self.insertParams(index, distance, startAngle, endAngle) # Add Rubber Band Line linePts = self.getLinePts(distance, self.capturedPoints[index-1], self.capturedPoints[index]) self.lineRb.addGeometry(QgsGeometry.fromPolyline( linePts ), None) self.formatTotal() def inMotion(self, pt): index = len(self.capturedPoints) if index <= 0: return (self.currentDistance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index-1], pt) self.insertParams(index, self.currentDistance, startAngle, endAngle) self.formatTotal() linePts = self.getLinePts(self.currentDistance, self.capturedPoints[index-1], pt) self.tempRb.setToGeometry(QgsGeometry.fromPolyline( linePts ), None) def calcParameters(self, pt1, pt2): l = self.geod.Inverse(pt1.y(), pt1.x(), pt2.y(), pt2.x()) az2 = (l['azi2'] + 180) %360.0 if az2 > 180: az2 = az2 - 360.0 l2 = self.geod.Inverse(pt2.y(), pt2.x(), pt1.y(), pt1.x()) return (l['s12'], l['azi1'], az2) def getLinePts(self, distance, pt1, pt2): canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(epsg4326, canvasCrs) pt1c = transform.transform(pt1.x(), pt1.y()) pt2c = transform.transform(pt2.x(), pt2.y()) if distance < 10000: return [pt1c, pt2c] l = self.geod.InverseLine(pt1.y(), pt1.x(), pt2.y(), pt2.x()) n = int(math.ceil(distance / 10000.0)) if n > 20: n = 20 seglen = distance / n pts = [pt1c] for i in range(1,n): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) ptc = transform.transform(g['lon2'], g['lat2']) pts.append( ptc ) pts.append(pt2c) return pts def insertParams(self, position, distance, startAngle, endAngle): if position > self.tableWidget.rowCount(): self.tableWidget.insertRow(position-1) item = QTableWidgetItem('{:.4f}'.format(self.unitDistance(distance))) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tableWidget.setItem(position-1, 2, item) item = QTableWidgetItem('{:.4f}'.format(startAngle)) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tableWidget.setItem(position-1, 0, item) item = QTableWidgetItem('{:.4f}'.format(endAngle)) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tableWidget.setItem(position-1, 1, item) def formatTotal(self): total = self.currentDistance ptcnt = len(self.capturedPoints) if ptcnt >= 2: i = 0 while i < ptcnt-1: total += self.distances[i] i += 1 self.distanceLineEdit.setText('{:.2f}'.format(self.unitDistance(total))) def clear(self): self.tableWidget.setRowCount(0) self.capturedPoints = [] self.distances = [] self.activeMeasuring = True self.currentDistance = 0.0 self.distanceLineEdit.setText('') self.pointRb.reset(QGis.Point) self.lineRb.reset(QGis.Line) self.tempRb.reset(QGis.Line) def unitDistance(self, distance): units = self.unitsComboBox.currentIndex() if units == 0: # meters return distance elif units == 1: # kilometers return distance / 1000.0 elif units == 2: # feet return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.Feet) elif units == 3: # yards return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.Feet) / 3.0 elif units == 4: # miles return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.Miles) else: # nautical miles return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.NauticalMiles)
class ReverseGeocodeTool(QgsMapTool): def __init__(self, iface, settings): self.canvas = iface.mapCanvas() QgsMapTool.__init__(self, self.canvas) self.iface = iface self.settings = settings self.reverseGeoCodeDialog = ReverseGeocodeDialog( self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.TopDockWidgetArea, self.reverseGeoCodeDialog) self.reverseGeoCodeDialog.hide() self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326') self.reply = None self.marker = None # Set up a polygon/line rubber band self.rubber = QgsRubberBand(self.canvas) self.rubber.setColor(QColor(255, 70, 0, 200)) self.rubber.setWidth(5) self.rubber.setBrushStyle(Qt.NoBrush) def activate(self): '''When activated set the cursor to a crosshair.''' self.canvas.setCursor(Qt.CrossCursor) self.show() def unload(self): self.iface.removeDockWidget(self.reverseGeoCodeDialog) self.reverseGeoCodeDialog = None if self.rubber: self.canvas.scene().removeItem(self.rubber) del self.rubber self.removeMarker() def addMarker(self, lat, lon): if self.marker: self.removeMarker() canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(self.epsg4326, canvasCrs, QgsProject.instance()) center = transform.transform(lon, lat) self.marker = QgsVertexMarker(self.canvas) self.marker.setCenter(center) self.marker.setColor(QColor(255, 70, 0)) self.marker.setIconSize(15) self.marker.setIconType(QgsVertexMarker.ICON_X) self.marker.setPenWidth(3) self.marker.show() def removeMarker(self): if self.marker: self.canvas.scene().removeItem(self.marker) self.marker = None def clearSelection(self): self.removeMarker() self.rubber.reset() def transform_geom(self, geometry): canvasCrs = self.canvas.mapSettings().destinationCrs() geom = QgsGeometry(geometry) geom.transform( QgsCoordinateTransform(self.epsg4326, canvasCrs, QgsProject.instance())) return geom def show(self): self.reverseGeoCodeDialog.show() def canvasReleaseEvent(self, event): # Make sure the point is transfored to 4326 pt = self.toMapCoordinates(event.pos()) canvasCRS = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(canvasCRS, self.epsg4326, QgsProject.instance()) pt = transform.transform(pt.x(), pt.y()) url = '{}?format=json&lat={:f}&lon={:f}&zoom={:d}&addressdetails=0&polygon_text=1'.format( self.settings.reverseURL(), pt.y(), pt.x(), self.settings.levelOfDetail) # print url qurl = QUrl(url) if self.reply is not None: self.reply.finished.disconnect(self.replyFinished) self.reply.abort() self.reply = None request = QNetworkRequest(qurl) request.setRawHeader( b"User-Agent", b"Mozilla/5.0 (Windows NT 6.1: WOW64; rv:52.0) Gecko/20100101 Firefox/52.0" ) request.setRawHeader(b"Connection", b"keep-alive") request.setRawHeader( b"Accept", b"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") self.reply = QgsNetworkAccessManager.instance().get(request) self.reply.finished.connect(self.replyFinished) if not self.reverseGeoCodeDialog.isVisible(): self.show() def setText(self, text): self.reverseGeoCodeDialog.addressLineEdit.setText(text) @pyqtSlot() def replyFinished(self): error = self.reply.error() self.clearSelection() if error == QNetworkReply.NoError: data = self.reply.readAll().data() try: jd = json.loads(data) try: display_name = jd['display_name'] self.setText(display_name) except KeyError: self.setText("[Could not find address]") try: wkt = jd['geotext'] geometry = QgsGeometry.fromWkt(wkt) geometry = self.transform_geom(geometry) self.rubber.addGeometry(geometry, None) self.rubber.show() except KeyError: try: lon = float(jd['lon']) lat = float(jd['lat']) self.addMarker(lat, lon) except: pass except: self.setText("Error: " + data) else: self.setText("[Address error]") self.reply.deleteLater() self.reply = None
class LatLonTools: digitizerDialog = None def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.crossRb.setColor(Qt.red) self.provider = LatLonToolsProvider() self.toolbar = self.iface.addToolBar('Lat Lon Tools Toolbar') self.toolbar.setObjectName('LatLonToolsToolbar') def initGui(self): '''Initialize Lot Lon Tools GUI.''' # Initialize the Settings Dialog box self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow()) self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface) self.showMapTool = ShowOnMapTool(self.iface) # Add Interface for Coordinate Capturing icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png") self.copyAction = QAction(icon, "Copy Latitude, Longitude", self.iface.mainWindow()) self.copyAction.setObjectName('latLonToolsCopy') self.copyAction.triggered.connect(self.startCapture) self.copyAction.setCheckable(True) self.toolbar.addAction(self.copyAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction) # Add Interface for External Map icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png") self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow()) self.externMapAction.setObjectName('latLonToolsExternalMap') self.externMapAction.triggered.connect(self.setShowMapTool) self.externMapAction.setCheckable(True) self.toolbar.addAction(self.externMapAction) self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction) # Add Interface for Zoom to Coordinate icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png") self.zoomToAction = QAction(icon, "Zoom To Latitude, Longitude", self.iface.mainWindow()) self.zoomToAction.setObjectName('latLonToolsZoom') self.zoomToAction.triggered.connect(self.showZoomToDialog) self.toolbar.addAction(self.zoomToAction) self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction) self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog) self.zoomToDialog.hide() # Add Interface for Multi point zoom icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png') self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow()) self.multiZoomToAction.setObjectName('latLonToolsMultiZoom') self.multiZoomToAction.triggered.connect(self.multiZoomTo) self.toolbar.addAction(self.multiZoomToAction) self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction) self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow()) self.multiZoomDialog.hide() self.multiZoomDialog.setFloating(True) # Create the conversions menu menu = QMenu() icon = QIcon(os.path.dirname(__file__) + '/images/field2geom.png') action = menu.addAction(icon, "Fields to point layer", self.field2geom) action.setObjectName('latLonToolsField2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.png') action = menu.addAction(icon, "Point layer to fields", self.geom2Field) action.setObjectName('latLonToolsGeom2Field') icon = QIcon(os.path.dirname(__file__) + '/images/pluscodes.png') action = menu.addAction(icon, "Plus Codes to point layer", self.PlusCodestoLayer) action.setObjectName('latLonToolsPlusCodes2Geom') action = menu.addAction(icon, "Point layer to Plus Codes", self.toPlusCodes) action.setObjectName('latLonToolsGeom2PlusCodes') icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png') action = menu.addAction(icon, "MGRS to point layer", self.MGRStoLayer) action.setObjectName('latLonToolsMGRS2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png') action = menu.addAction(icon, "Point layer to MGRS", self.toMGRS) action.setObjectName('latLonToolsGeom2MGRS') self.conversionsAction = QAction(icon, "Conversions", self.iface.mainWindow()) self.conversionsAction.setMenu(menu) self.iface.addPluginToMenu('Lat Lon Tools', self.conversionsAction) # Add to Digitize Toolbar icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.png') self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow()) self.digitizeAction.setObjectName('latLonToolsDigitize') self.digitizeAction.triggered.connect(self.digitizeClicked) self.digitizeAction.setEnabled(False) self.toolbar.addAction(self.digitizeAction) self.iface.addPluginToMenu('Lat Lon Tools', self.digitizeAction) # Add Interface for copying the canvas extent icon = QIcon(os.path.dirname(__file__) + "/images/copycanvas.png") self.copyCanvasAction = QAction(icon, "Copy Canvas Bounding Box", self.iface.mainWindow()) self.copyCanvasAction.setObjectName('latLonToolsCopyCanvas') self.copyCanvasAction.triggered.connect(self.copyCanvas) self.toolbar.addAction(self.copyCanvasAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyCanvasAction) # Initialize the Settings Dialog Box settingsicon = QIcon(os.path.dirname(__file__) + '/images/settings.png') self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow()) self.settingsAction.setObjectName('latLonToolsSettings') self.settingsAction.triggered.connect(self.settings) self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction) # Help icon = QIcon(os.path.dirname(__file__) + '/images/help.png') self.helpAction = QAction(icon, "Help", self.iface.mainWindow()) self.helpAction.setObjectName('latLonToolsHelp') self.helpAction.triggered.connect(self.help) self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction) self.iface.currentLayerChanged.connect(self.currentLayerChanged) self.canvas.mapToolSet.connect(self.unsetTool) self.enableDigitizeTool() # Add the processing provider QgsApplication.processingRegistry().addProvider(self.provider) def unsetTool(self, tool): '''Uncheck the Copy Lat Lon tool''' try: if not isinstance(tool, CopyLatLonTool): self.copyAction.setChecked(False) self.multiZoomDialog.stopCapture() self.mapTool.capture4326 = False if not isinstance(tool, ShowOnMapTool): self.externMapAction.setChecked(False) except: pass def unload(self): '''Unload LatLonTools from the QGIS interface''' self.zoomToDialog.removeMarker() self.multiZoomDialog.removeMarkers() self.canvas.unsetMapTool(self.mapTool) self.canvas.unsetMapTool(self.showMapTool) self.iface.removePluginMenu('Lat Lon Tools', self.copyAction) self.iface.removePluginMenu('Lat Lon Tools', self.copyCanvasAction) self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction) self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.conversionsAction) self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction) self.iface.removePluginMenu('Lat Lon Tools', self.helpAction) self.iface.removePluginMenu('Lat Lon Tools', self.digitizeAction) self.iface.removeDockWidget(self.zoomToDialog) self.iface.removeDockWidget(self.multiZoomDialog) # Remove Toolbar Icons self.iface.removeToolBarIcon(self.copyAction) self.iface.removeToolBarIcon(self.copyCanvasAction) self.iface.removeToolBarIcon(self.zoomToAction) self.iface.removeToolBarIcon(self.externMapAction) self.iface.removeToolBarIcon(self.multiZoomToAction) self.iface.removeToolBarIcon(self.digitizeAction) del self.toolbar self.zoomToDialog = None self.multiZoomDialog = None self.settingsDialog = None self.showMapTool = None self.mapTool = None self.digitizerDialog = None QgsApplication.processingRegistry().removeProvider(self.provider) def startCapture(self): '''Set the focus of the copy coordinate tool and check it''' self.copyAction.setChecked(True) self.canvas.setMapTool(self.mapTool) def copyCanvas(self): extent = self.iface.mapCanvas().extent() canvasCrs = self.canvas.mapSettings().destinationCrs() if settings.bBoxCrs == 0 and canvasCrs != epsg4326: transform = QgsCoordinateTransform(canvasCrs, epsg4326, QgsProject.instance()) p1x, p1y = transform.transform(float(extent.xMinimum()), float(extent.yMinimum())) p2x, p2y = transform.transform(float(extent.xMaximum()), float(extent.yMaximum())) extent.set(p1x, p1y, p2x, p2y) delim = settings.bBoxDelimiter prefix = settings.bBoxPrefix suffix = settings.bBoxSuffix precision = settings.bBoxDigits outStr = '' minX = extent.xMinimum() minY = extent.yMinimum() maxX = extent.xMaximum() maxY = extent.yMaximum() if settings.bBoxFormat == 0: # minX,minY,maxX,maxY - using the delimiter outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format( minX, delim, minY, delim, maxX, delim, maxY, prec=precision) elif settings.bBoxFormat == 1: # minX,maxX,minY,maxY - Using the selected delimiter' outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format( minX, delim, maxX, delim, minY, delim, maxY, prec=precision) elif settings.bBoxFormat == 2: # x1 y1,x2 y2,x3 y3,x4 y4,x1 y1 - Polygon format outStr = '{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f}'.format( minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision) elif settings.bBoxFormat == 3: # x1,y1 x2,y2 x3,y3 x4,y4 x1,y1 - Polygon format outStr = '{:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f}'.format( minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision) elif settings.bBoxFormat == 4: # WKT Polygon outStr = extent.asWktPolygon() elif settings.bBoxFormat == 5: # bbox: [minX, minY, maxX, maxY] - MapProxy outStr = 'bbox: [{}, {}, {}, {}]'.format( minX, minY, maxX, maxY) elif settings.bBoxFormat == 6: # bbox: [minX, minY, maxX, maxY] - MapProxy outStr = 'bbox={},{},{},{}'.format( minX, minY, maxX, maxY) outStr = '{}{}{}'.format(prefix, outStr, suffix) clipboard = QApplication.clipboard() clipboard.setText(outStr) self.iface.messageBar().pushMessage("", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4) def setShowMapTool(self): '''Set the focus of the external map tool and check it''' self.externMapAction.setChecked(True) self.canvas.setMapTool(self.showMapTool) def showZoomToDialog(self): '''Show the zoom to docked widget.''' self.zoomToDialog.show() def multiZoomTo(self): '''Display the Multi-zoom to dialog box''' self.multiZoomDialog.show() def field2geom(self): '''Convert layer containing a point x & y coordinate to a new point layer''' results = processing.execAlgorithmDialog('latlontools:field2geom', {}) def geom2Field(self): '''Convert layer geometry to a text string''' results = processing.execAlgorithmDialog('latlontools:geom2field', {}) def toMGRS(self): '''Display the to MGRS dialog box''' results = processing.execAlgorithmDialog('latlontools:point2mgrs', {}) def MGRStoLayer(self): '''Display the to MGRS dialog box''' results = processing.execAlgorithmDialog('latlontools:mgrs2point', {}) def toPlusCodes(self): results = processing.execAlgorithmDialog('latlontools:point2pluscodes', {}) def PlusCodestoLayer(self): results = processing.execAlgorithmDialog('latlontools:pluscodes2point', {}) def settings(self): '''Show the settings dialog box''' self.settingsDialog.show() def help(self): '''Display a help page''' url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2) def settingsChanged(self): # Settings may have changed so we need to make sure the zoomToDialog window is configured properly self.zoomToDialog.configure() self.multiZoomDialog.settingsChanged() def zoomTo(self, srcCrs, lat, lon): canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(srcCrs, canvasCrs, QgsProject.instance()) x, y = transform.transform(float(lon), float(lat)) rect = QgsRectangle(x, y, x, y) self.canvas.setExtent(rect) pt = QgsPointXY(x, y) self.highlight(pt) self.canvas.refresh() return pt def highlight(self, point): currExt = self.canvas.extent() leftPt = QgsPoint(currExt.xMinimum(), point.y()) rightPt = QgsPoint(currExt.xMaximum(), point.y()) topPt = QgsPoint(point.x(), currExt.yMaximum()) bottomPt = QgsPoint(point.x(), currExt.yMinimum()) horizLine = QgsGeometry.fromPolyline( [ leftPt , rightPt ] ) vertLine = QgsGeometry.fromPolyline( [ topPt , bottomPt ] ) self.crossRb.reset(QgsWkbTypes.LineGeometry) self.crossRb.addGeometry(horizLine, None) self.crossRb.addGeometry(vertLine, None) QTimer.singleShot(700, self.resetRubberbands) def resetRubberbands(self): self.crossRb.reset() def digitizeClicked(self): if self.digitizerDialog == None: from .digitizer import DigitizerWidget self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow()) self.digitizerDialog.show() def currentLayerChanged(self): layer = self.iface.activeLayer() if layer != None: try: layer.editingStarted.disconnect(self.layerEditingChanged) except: pass try: layer.editingStopped.disconnect(self.layerEditingChanged) except: pass if isinstance(layer, QgsVectorLayer): layer.editingStarted.connect(self.layerEditingChanged) layer.editingStopped.connect(self.layerEditingChanged) self.enableDigitizeTool() def layerEditingChanged(self): self.enableDigitizeTool() def enableDigitizeTool(self): self.digitizeAction.setEnabled(False) layer = self.iface.activeLayer() if layer != None and isinstance(layer, QgsVectorLayer) and (layer.geometryType() == QgsWkbTypes.PointGeometry) and layer.isEditable(): self.digitizeAction.setEnabled(True) else: if self.digitizerDialog != None: self.digitizerDialog.close()
class GeomapfishLocatorFilter(QgsLocatorFilter): USER_AGENT = b'Mozilla/5.0 QGIS GeoMapFish Locator Filter' changed = pyqtSignal() def __init__(self, service: Service, iface: QgisInterface = None): super().__init__() self.service = service.clone() self.rubberband = None self.iface = None self.map_canvas = None self.current_timer = None self.settings = Settings() self.crs = QgsCoordinateReferenceSystem(self.service.crs) # only get map_canvas on main thread, not when cloning if iface is not None: self.iface = iface self.map_canvas = iface.mapCanvas() self.rubberband = QgsRubberBand(self.map_canvas) self.reset_rubberband() def name(self) -> str: return slugify(self.displayName()) def clone(self): return GeomapfishLocatorFilter(self.service) def displayName(self) -> str: return self.service.name def prefix(self) -> str: return 'gmf' def hasConfigWidget(self) -> bool: return True def openConfigWidget(self, parent=None): cfg = FilterConfigurationDialog(self.service, parent) if cfg.exec_(): self.service = cfg.service.clone() self.crs = QgsCoordinateReferenceSystem(self.service.crs) self.changed.emit() def reset_rubberband(self): # this should happen on main thread only! self.rubberband.setColor(self.settings.value('point_color')) self.rubberband.setIcon(self.rubberband.ICON_CIRCLE) self.rubberband.setIconSize(self.settings.value('point_size')) self.rubberband.setWidth(self.settings.value('line_width')) self.rubberband.setBrushStyle(Qt.NoBrush) @staticmethod def url_with_param(url, params) -> str: url = QUrl(url) q = QUrlQuery(url) for key, value in params.items(): q.addQueryItem(key, value) url.setQuery(q) return url def emit_bad_configuration(self, err=None): result = QgsLocatorResult() result.filter = self result.displayString = self.tr('Locator filter is not configured.') result.description = err if err else self.tr( 'Double-click to configure it.') result.userData = FilterNotConfigured result.icon = QgsApplication.getThemeIcon('mIconWarning.svg') self.resultFetched.emit(result) return @pyqtSlot() def clear_results(self): if self.rubberband: self.rubberband.reset(QgsWkbTypes.PointGeometry) if self.current_timer is not None: self.current_timer.timeout.disconnect(self.clear_results) self.current_timer.stop() self.current_timer.deleteLater() self.current_timer = None def fetchResults(self, search, context, feedback): self.dbg_info("start GMF locator search...") url = self.service.url if not url: self.emit_bad_configuration() return if len(search) < 2: return params = { 'query': search, 'limit': str(self.service.total_limit), 'partitionlimit': str(self.service.category_limit) } url = self.url_with_param(url, params) self.dbg_info(url.url()) _request = QNetworkRequest(url) _request.setRawHeader(b'User-Agent', self.USER_AGENT) request = QgsBlockingNetworkRequest() if self.service.authid: request.setAuthCfg(self.service.authid) response = request.get(_request, False, feedback) if response == QgsBlockingNetworkRequest.NoError: self.handle_response(request.reply().content()) else: error_msg = request.reply().errorString() self.emit_bad_configuration(error_msg) self.info(error_msg, Qgis.Critical) self.finished.emit() def handle_response(self, content: QByteArray): try: data = json.loads(str(content.data(), encoding='utf-8')) # self.dbg_info(data) features = data['features'] for f in features: json_geom = json.dumps(f['geometry']) ogr_geom = ogr.CreateGeometryFromJson(json_geom) wkt = ogr_geom.ExportToWkt() geometry = QgsGeometry.fromWkt(wkt) self.dbg_info('---------') self.dbg_info( QgsWkbTypes.geometryDisplayString(geometry.type())) self.dbg_info(f.keys()) self.dbg_info('{} {}'.format(f['properties']['layer_name'], f['properties']['label'])) self.dbg_info(f['bbox']) self.dbg_info(f['geometry']) if geometry is None: continue result = QgsLocatorResult() result.filter = self result.displayString = f['properties']['label'] result.group = self.beautify_group( f['properties']['layer_name']) result.userData = geometry self.resultFetched.emit(result) except Exception as e: self.info(str(e), Qgis.Critical) # exc_type, exc_obj, exc_traceback = sys.exc_info() # #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1] # #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) # self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def triggerResult(self, result): self.clear_results() if result.userData == FilterNotConfigured: self.openConfigWidget() self.iface.invalidateLocatorResults() return # this should be run in the main thread, i.e. mapCanvas should not be None geometry = result.userData # geometry.transform(self.transform) dbg_info(str(geometry.asWkt())) dbg_info(geometry.type()) try: rect = QgsReferencedRectangle(geometry.boundingBox(), self.crs) rect.scale(4) self.map_canvas.setReferencedExtent(rect) except AttributeError: # QGIS < 3.10 handling from qgis.core import QgsCoordinateTransform, QgsProject transform = QgsCoordinateTransform( self.crs, self.map_canvas.mapSettings().destinationCrs(), QgsProject.instance()) geometry.transform(transform) rect = geometry.boundingBox() rect.scale(4) self.map_canvas.setExtent(rect) self.map_canvas.refresh() if geometry.type() == QgsWkbTypes.PolygonGeometry: nflash = 16 color1: QColor = self.settings.value('polygon_color') color2 = color1 color1.setAlpha(200) color2.setAlpha(100) self.map_canvas.flashGeometries( [geometry], self.crs, color1, color2, nflash, self.settings.value('highlight_duration') / nflash * 1000) else: self.rubberband.reset(geometry.type()) self.rubberband.addGeometry(geometry, self.crs) self.current_timer = QTimer() self.current_timer.timeout.connect(self.clear_results) self.current_timer.setSingleShot(True) self.current_timer.start( self.settings.value('highlight_duration') * 1000) def beautify_group(self, group) -> str: if self.service.remove_leading_digits: group = re.sub('^[0-9]+', '', group) if self.service.replace_underscore: group = group.replace("_", " ") if self.service.break_camelcase: group = self.break_camelcase(group) return group def info(self, msg="", level=Qgis.Info): QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg), 'QgsLocatorFilter', level) def dbg_info(self, msg=""): if DEBUG: self.info(msg) @staticmethod def break_camelcase(identifier) -> str: matches = re.finditer( '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', identifier) return ' '.join([m.group(0) for m in matches])
class Qgis2threejsDialog(QDialog): STYLE_MAX_COUNT = 4 def __init__(self, iface, properties=None): QDialog.__init__(self, iface.mainWindow()) self.iface = iface self.templateType = None self.currentItem = None self.currentPage = None topItemCount = len(ObjectTreeItem.topItemNames) if properties is None: self.properties = [None] * topItemCount for i in range(ObjectTreeItem.ITEM_OPTDEM, topItemCount): self.properties[i] = {} else: self.properties = properties # Set up the user interface from Designer. self.ui = ui = Ui_Qgis2threejsDialog() ui.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint) ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]") ui.progressBar.setVisible(False) ui.label_MessageIcon.setVisible(False) ui.pushButton_Run.clicked.connect(self.run) ui.pushButton_Close.clicked.connect(self.reject) # set up map tool self.previousMapTool = None self.mapTool = RectangleMapTool(iface.mapCanvas()) #self.mapTool = PointMapTool(iface.mapCanvas()) # set up the template combo box self.initTemplateList() self.ui.comboBox_Template.currentIndexChanged.connect(self.currentTemplateChanged) # set up the properties pages self.pages = {} self.pages[ppages.PAGE_WORLD] = ppages.WorldPropertyPage(self) self.pages[ppages.PAGE_CONTROLS] = ppages.ControlsPropertyPage(self) self.pages[ppages.PAGE_DEM] = ppages.DEMPropertyPage(self) self.pages[ppages.PAGE_VECTOR] = ppages.VectorPropertyPage(self) container = ui.propertyPagesContainer for page in self.pages.itervalues(): page.hide() container.addWidget(page) # build object tree self.topItemPages = {ObjectTreeItem.ITEM_WORLD: ppages.PAGE_WORLD, ObjectTreeItem.ITEM_CONTROLS: ppages.PAGE_CONTROLS, ObjectTreeItem.ITEM_DEM: ppages.PAGE_DEM} self.initObjectTree() self.ui.treeWidget.currentItemChanged.connect(self.currentObjectChanged) self.ui.treeWidget.itemChanged.connect(self.objectItemChanged) self.currentTemplateChanged() # update item visibility ui.toolButton_Browse.clicked.connect(self.browseClicked) #iface.mapCanvas().mapToolSet.connect(self.mapToolSet) # to show button to enable own map tool self.localBrowsingMode = True self.rb_quads = self.rb_point = None self.objectTypeManager = ObjectTypeManager() def showMessageBar(self, text, level=QgsMessageBar.INFO): # from src/gui/qgsmessagebaritem.cpp if level == QgsMessageBar.CRITICAL: msgIcon = "/mIconCritical.png" bgColor = "#d65253" elif level == QgsMessageBar.WARNING: msgIcon = "/mIconWarn.png" bgColor = "#ffc800" else: msgIcon = "/mIconInfo.png" bgColor = "#e7f5fe" stylesheet = "QLabel {{ background-color:{0}; }}".format(bgColor) label = self.ui.label_MessageIcon label.setPixmap(QgsApplication.getThemeIcon(msgIcon).pixmap(24)) label.setStyleSheet(stylesheet) label.setVisible(True) label = self.ui.label_Status label.setText(text) label.setStyleSheet(stylesheet) def clearMessageBar(self): self.ui.label_MessageIcon.setVisible(False) self.ui.label_Status.setText("") self.ui.label_Status.setStyleSheet("QLabel { background-color: rgba(0, 0, 0, 0); }") def initTemplateList(self): cbox = self.ui.comboBox_Template cbox.clear() templateDir = QDir(tools.templateDir()) for i, entry in enumerate(templateDir.entryList(["*.html", "*.htm"])): cbox.addItem(entry) config = tools.getTemplateConfig(templateDir.filePath(entry)) # get template type templateType = config.get("type", "plain") cbox.setItemData(i, templateType, Qt.UserRole) # set tool tip text desc = config.get("description", "") if desc: cbox.setItemData(i, desc, Qt.ToolTipRole) # select the last used template templateName = QSettings().value("/Qgis2threejs/lastTemplate", "", type=unicode) if templateName: index = cbox.findText(templateName) if index != -1: cbox.setCurrentIndex(index) return index return -1 def initObjectTree(self): tree = self.ui.treeWidget tree.clear() # add vector and raster layers into tree widget topItems = [] for index, itemName in enumerate(ObjectTreeItem.topItemNames): item = QTreeWidgetItem(tree, [itemName]) item.setData(0, Qt.UserRole, index) topItems.append(item) optDEMChecked = False for layer in self.iface.legendInterface().layers(): layerType = layer.type() if layerType not in (QgsMapLayer.VectorLayer, QgsMapLayer.RasterLayer): continue parentId = None if layerType == QgsMapLayer.VectorLayer: geometry_type = layer.geometryType() if geometry_type in [QGis.Point, QGis.Line, QGis.Polygon]: parentId = ObjectTreeItem.ITEM_POINT + geometry_type # - QGis.Point elif layerType == QgsMapLayer.RasterLayer and layer.providerType() == "gdal" and layer.bandCount() == 1: parentId = ObjectTreeItem.ITEM_OPTDEM if parentId is None: continue item = QTreeWidgetItem(topItems[parentId], [layer.name()]) isVisible = self.properties[parentId].get(layer.id(), {}).get("visible", False) #self.iface.legendInterface().isLayerVisible(layer) check_state = Qt.Checked if isVisible else Qt.Unchecked item.setData(0, Qt.CheckStateRole, check_state) item.setData(0, Qt.UserRole, layer.id()) if parentId == ObjectTreeItem.ITEM_OPTDEM and isVisible: optDEMChecked = True for item in topItems: if item.data(0, Qt.UserRole) != ObjectTreeItem.ITEM_OPTDEM or optDEMChecked: tree.expandItem(item) def saveProperties(self, item, page): properties = page.properties() parent = item.parent() if parent is None: # top item: properties[topItemIndex] self.properties[item.data(0, Qt.UserRole)] = properties else: # layer item: properties[topItemIndex][layerId] topItemIndex = parent.data(0, Qt.UserRole) self.properties[topItemIndex][item.data(0, Qt.UserRole)] = properties if debug_mode: qDebug(str(self.properties)) def currentTemplateChanged(self, index=None): cbox = self.ui.comboBox_Template templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole) if templateType == self.templateType: return tree = self.ui.treeWidget for i, name in enumerate(ObjectTreeItem.topItemNames): hidden = (templateType == "sphere" and name != "Controls") tree.topLevelItem(i).setHidden(hidden) itemToBeSelected = ObjectTreeItem.ITEM_CONTROLS if templateType == "sphere" else ObjectTreeItem.ITEM_DEM tree.setCurrentItem(tree.topLevelItem(itemToBeSelected)) self.clearMessageBar() if templateType != "sphere": # show message if crs unit is degrees mapSettings = self.iface.mapCanvas().mapSettings() if QGis.QGIS_VERSION_INT >= 20300 else self.iface.mapCanvas().mapRenderer() if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]: self.showMessageBar("The unit of current CRS is degrees, so terrain will not appear well.", QgsMessageBar.WARNING) self.templateType = templateType def currentObjectChanged(self, currentItem, previousItem): # save properties of previous item if previousItem and self.currentPage: self.saveProperties(previousItem, self.currentPage) self.currentItem = currentItem self.currentPage = None # hide all pages for page in self.pages.itervalues(): page.hide() parent = currentItem.parent() if parent is None: topItemIndex = currentItem.data(0, Qt.UserRole) pageType = self.topItemPages.get(topItemIndex, ppages.PAGE_NONE) page = self.pages.get(pageType, None) if page is None: return page.setup(self.properties[topItemIndex]) page.show() else: parentId = parent.data(0, Qt.UserRole) layerId = currentItem.data(0, Qt.UserRole) if layerId is None: return layer = QgsMapLayerRegistry.instance().mapLayer(layerId) if layer is None: return layerType = layer.type() if layerType == QgsMapLayer.RasterLayer: page = self.pages[ppages.PAGE_DEM] page.setup(self.properties[parentId].get(layerId, None), layer, False) elif layerType == QgsMapLayer.VectorLayer: page = self.pages[ppages.PAGE_VECTOR] page.setup(self.properties[parentId].get(layerId, None), layer) else: return page.show() self.currentPage = page def objectItemChanged(self, item, column): parent = item.parent() if parent is None: return if item == self.currentItem: if self.currentPage: # update enablement of property widgets self.currentPage.itemChanged(item) else: # select changed item self.ui.treeWidget.setCurrentItem(item) # set visible property #visible = item.data(0, Qt.CheckStateRole) == Qt.Checked #parentId = parent.data(0, Qt.UserRole) #layerId = item.data(0, Qt.UserRole) #self.properties[parentId].get(layerId, {})["visible"] = visible def primaryDEMChanged(self, layerId): tree = self.ui.treeWidget parent = tree.topLevelItem(ObjectTreeItem.ITEM_OPTDEM) tree.blockSignals(True) for i in range(parent.childCount()): item = parent.child(i) isPrimary = item.data(0, Qt.UserRole) == layerId item.setDisabled(isPrimary) tree.blockSignals(False) def numericFields(self, layer): # get attributes of a sample feature and create numeric field name list numeric_fields = [] f = QgsFeature() layer.getFeatures().nextFeature(f) for field in f.fields(): isNumeric = False try: float(f.attribute(field.name())) isNumeric = True except: pass if isNumeric: numeric_fields.append(field.name()) return numeric_fields def progress(self, percentage, statusMsg=None): ui = self.ui ui.progressBar.setValue(percentage) if percentage == 100: ui.progressBar.setVisible(False) ui.label_Status.setText("") else: ui.progressBar.setVisible(True) if statusMsg is not None: ui.label_Status.setText(statusMsg) ui.label_Status.repaint() def run(self): ui = self.ui filename = ui.lineEdit_OutputFilename.text() # ""=Temporary file if filename != "" and QFileInfo(filename).exists() and QMessageBox.question(None, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok: return self.endPointSelection() # save properties of current object item = self.ui.treeWidget.currentItem() if item and self.currentPage: self.saveProperties(item, self.currentPage) ui.pushButton_Run.setEnabled(False) self.clearMessageBar() self.progress(0) canvas = self.iface.mapCanvas() cbox = self.ui.comboBox_Template templateName = cbox.currentText() templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole) htmlfilename = ui.lineEdit_OutputFilename.text() # world properties world = self.properties[ObjectTreeItem.ITEM_WORLD] or {} verticalExaggeration = world.get("lineEdit_zFactor", 1.5) verticalShift = world.get("lineEdit_zShift", 0) # export to javascript (three.js) mapTo3d = MapTo3D(canvas, verticalExaggeration=float(verticalExaggeration), verticalShift=float(verticalShift)) context = OutputContext(templateName, templateType, mapTo3d, canvas, self.properties, self, self.objectTypeManager, self.localBrowsingMode) htmlfilename = exportToThreeJS(htmlfilename, context, self.progress) self.progress(100) ui.pushButton_Run.setEnabled(True) if htmlfilename is None: return self.clearRubberBands() # store last selections settings = QSettings() settings.setValue("/Qgis2threejs/lastTemplate", templateName) settings.setValue("/Qgis2threejs/lastControls", context.controls) # open browser if not tools.openHTMLFile(htmlfilename): return QDialog.accept(self) def reject(self): # save properties of current object item = self.ui.treeWidget.currentItem() if item and self.currentPage: self.saveProperties(item, self.currentPage) self.endPointSelection() self.clearRubberBands() QDialog.reject(self) def startPointSelection(self): canvas = self.iface.mapCanvas() if self.previousMapTool != self.mapTool: self.previousMapTool = canvas.mapTool() canvas.setMapTool(self.mapTool) self.pages[ppages.PAGE_DEM].toolButton_PointTool.setVisible(False) def endPointSelection(self): self.mapTool.reset() if self.previousMapTool is not None: self.iface.mapCanvas().setMapTool(self.previousMapTool) def mapToolSet(self, mapTool): return #TODO: unstable if mapTool != self.mapTool and self.currentPage is not None: if self.currentPage.pageType == ppages.PAGE_DEM and self.currentPage.isPrimary: self.currentPage.toolButton_PointTool.setVisible(True) def createRubberBands(self, quads, point=None): self.clearRubberBands() # create quads with rubber band self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line) self.rb_quads.setColor(Qt.blue) self.rb_quads.setWidth(1) for quad in quads: points = [] extent = quad.extent points.append(QgsPoint(extent.xMinimum(), extent.yMinimum())) points.append(QgsPoint(extent.xMinimum(), extent.yMaximum())) points.append(QgsPoint(extent.xMaximum(), extent.yMaximum())) points.append(QgsPoint(extent.xMaximum(), extent.yMinimum())) self.rb_quads.addGeometry(QgsGeometry.fromPolygon([points]), None) self.log(extent.toString()) self.log("Quad count: %d" % len(quads)) # create a point with rubber band if point: self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rb_point.setColor(Qt.red) self.rb_point.addPoint(point) def clearRubberBands(self): # clear quads and point if self.rb_quads: self.iface.mapCanvas().scene().removeItem(self.rb_quads) self.rb_quads = None if self.rb_point: self.iface.mapCanvas().scene().removeItem(self.rb_point) self.rb_point = None def browseClicked(self): directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0] if directory == "": directory = QDir.homePath() filename = QFileDialog.getSaveFileName(self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite) if filename != "": self.ui.lineEdit_OutputFilename.setText(filename) def log(self, msg): if debug_mode: qDebug(msg)
class LatLonTools: def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.crossRb.setColor(Qt.red) def initGui(self): '''Initialize Lot Lon Tools GUI.''' # Initialize the Settings Dialog box self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow()) self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface) self.showMapTool = ShowOnMapTool(self.settingsDialog, self.iface) self.toMGRSDialog = ToMGRSWidget(self.iface, self.iface.mainWindow()) self.MGRStoLayerDialog = MGRStoLayerWidget(self.iface, self.iface.mainWindow()) # Add Interface for Coordinate Capturing icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png") self.copyAction = QAction(icon, "Copy Latitude, Longitude", self.iface.mainWindow()) self.copyAction.triggered.connect(self.startCapture) self.copyAction.setCheckable(True) self.iface.addToolBarIcon(self.copyAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction) # Add Interface for Zoom to Coordinate icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png") self.zoomToAction = QAction(icon, "Zoom To Latitude, Longitude", self.iface.mainWindow()) self.zoomToAction.triggered.connect(self.showZoomToDialog) self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction) self.canvas.mapToolSet.connect(self.unsetTool) self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog) self.zoomToDialog.hide() # Add Interface for External Map icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png") self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow()) self.externMapAction.triggered.connect(self.setShowMapTool) self.externMapAction.setCheckable(True) self.iface.addToolBarIcon(self.externMapAction) self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction) # Add Interface for Multi point zoom icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png') self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow()) self.multiZoomToAction.triggered.connect(self.multiZoomTo) self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction) self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow()) self.multiZoomDialog.hide() self.multiZoomDialog.setFloating(True) # Add To MGRS conversion icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png') icon2 = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png') menu = QMenu() menu.addAction(icon, "MGRS to Geometry", self.MGRStoLayer) menu.addAction(icon2, "Geometry to MGRS", self.toMGRS) self.toMGRSAction = QAction(icon2, "MGRS Conversions", self.iface.mainWindow()) self.toMGRSAction.setMenu(menu) self.iface.addPluginToMenu('Lat Lon Tools', self.toMGRSAction) # Initialize the Settings Dialog Box settingsicon = QIcon( os.path.dirname(__file__) + '/images/settings.png') self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow()) self.settingsAction.triggered.connect(self.settings) self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction) # Help helpicon = QIcon(os.path.dirname(__file__) + '/images/help.png') self.helpAction = QAction(helpicon, "Help", self.iface.mainWindow()) self.helpAction.triggered.connect(self.help) self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction) def unsetTool(self, tool): '''Uncheck the Copy Lat Lon tool''' try: if not isinstance(tool, CopyLatLonTool): self.copyAction.setChecked(False) self.multiZoomDialog.stopCapture() self.mapTool.capture4326 = False if not isinstance(tool, ShowOnMapTool): self.externMapAction.setChecked(False) except: pass def unload(self): '''Unload LatLonTools from the QGIS interface''' self.zoomToDialog.removeMarker() self.multiZoomDialog.removeMarkers() self.canvas.unsetMapTool(self.mapTool) self.canvas.unsetMapTool(self.showMapTool) self.iface.removePluginMenu('Lat Lon Tools', self.copyAction) self.iface.removeToolBarIcon(self.copyAction) self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction) self.iface.removeToolBarIcon(self.externMapAction) self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.toMGRSAction) self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction) self.iface.removePluginMenu('Lat Lon Tools', self.helpAction) self.iface.removeDockWidget(self.zoomToDialog) self.iface.removeDockWidget(self.multiZoomDialog) self.zoomToDialog = None self.multiZoomDialog = None def startCapture(self): '''Set the focus of the copy coordinate tool and check it''' self.copyAction.setChecked(True) self.canvas.setMapTool(self.mapTool) def setShowMapTool(self): '''Set the focus of the external map tool and check it''' self.externMapAction.setChecked(True) self.canvas.setMapTool(self.showMapTool) def showZoomToDialog(self): '''Show the zoom to docked widget.''' self.zoomToDialog.show() def multiZoomTo(self): '''Display the Multi-zoom to dialog box''' self.multiZoomDialog.show() def toMGRS(self): '''Display the to MGRS dialog box''' self.toMGRSDialog.show() def MGRStoLayer(self): '''Display the to MGRS dialog box''' self.MGRStoLayerDialog.show() def settings(self): '''Show the settings dialog box''' self.settingsDialog.show() def help(self): '''Display a help page''' url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2) def settingsChanged(self): # Settings may have changed so we need to make sure the zoomToDialog window is configured properly self.zoomToDialog.configure() def zoomTo(self, srcCrs, lat, lon): canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(srcCrs, canvasCrs) x, y = transform.transform(float(lon), float(lat)) rect = QgsRectangle(x, y, x, y) self.canvas.setExtent(rect) pt = QgsPoint(x, y) self.highlight(pt) self.canvas.refresh() return pt def highlight(self, point): currExt = self.canvas.extent() leftPt = QgsPoint(currExt.xMinimum(), point.y()) rightPt = QgsPoint(currExt.xMaximum(), point.y()) topPt = QgsPoint(point.x(), currExt.yMaximum()) bottomPt = QgsPoint(point.x(), currExt.yMinimum()) horizLine = QgsGeometry.fromPolyline([leftPt, rightPt]) vertLine = QgsGeometry.fromPolyline([topPt, bottomPt]) self.crossRb.reset(QgsWkbTypes.LineGeometry) self.crossRb.addGeometry(horizLine, None) self.crossRb.addGeometry(vertLine, None) QTimer.singleShot(700, self.resetRubberbands) def resetRubberbands(self): self.crossRb.reset()
class EditTool(MapTool): """ Inspection tool which copies the feature to a new layer and copies selected data from the underlying feature. """ finished = pyqtSignal(QgsVectorLayer, QgsFeature) def __init__(self, canvas, layers, snapradius = 2): MapTool.__init__(self, canvas, layers) self.canvas = canvas self.radius = snapradius self.band = QgsRubberBand(self.canvas) self.band.setColor(QColor.fromRgb(224,162,16)) self.band.setWidth(3) self.cursor = QCursor(QPixmap(["16 16 3 1", " c None", ". c #FF0000", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ "])) def getFeatures(self, point): searchRadius = (QgsTolerance.toleranceInMapUnits( self.radius, self.layers[0], self.canvas.mapRenderer(), QgsTolerance.Pixels)) point = self.toMapCoordinates(point) rect = QgsRectangle() rect.setXMinimum(point.x() - searchRadius) rect.setXMaximum(point.x() + searchRadius) rect.setYMinimum(point.y() - searchRadius) rect.setYMaximum(point.y() + searchRadius) rq = QgsFeatureRequest().setFilterRect(rect) self.band.reset() for layer in self.layers: rq = QgsFeatureRequest().setFilterRect(rect) for feature in layer.getFeatures(rq): if feature.isValid(): yield feature, layer def canvasMoveEvent(self, event): for feature, _ in self.getFeatures(point = event.pos()): self.band.addGeometry(feature.geometry(), None) def canvasReleaseEvent(self, event): features = list(self.getFeatures(point = event.pos())) if len(features) == 1: feature = features[0] self.finished.emit(feature[1], feature[0]) elif len(features) > 0: listUi = ListFeaturesForm() listUi.loadFeatureList(features) listUi.openFeatureForm.connect(self.finished) listUi.exec_() 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.canvas.setCursor(self.cursor) def deactivate(self): """ Deactive the tool. """ pass def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True
class AdvancedIntersectionMapTool(QgsMapTool): def __init__(self, iface): self.iface = iface self.mapCanvas = iface.mapCanvas() QgsMapTool.__init__(self, self.mapCanvas) self.settings = MySettings() self.rubber = QgsRubberBand(self.mapCanvas) self.tolerance = self.settings.value("selectTolerance") units = self.settings.value("selectUnits") if units == "pixels": self.tolerance *= self.mapCanvas.mapUnitsPerPixel() def activate(self): QgsMapTool.activate(self) self.rubber.setWidth(self.settings.value("rubberWidth")) self.rubber.setColor(self.settings.value("rubberColor")) lineLayer = MemoryLayers(self.iface).lineLayer() # unset this tool if the layer is removed lineLayer.layerDeleted.connect(self.unsetMapTool) self.layerId = lineLayer.id() # create snapper for this layer self.snapLayer = QgsSnapper.SnapLayer() self.snapLayer.mLayer = lineLayer self.snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment self.snapLayer.mTolerance = self.settings.value("selectTolerance") if self.settings.value("selectUnits") == "map": self.snapLayer.mUnitType = QgsTolerance.MapUnits else: self.snapLayer.mUnitType = QgsTolerance.Pixels def unsetMapTool(self): self.mapCanvas.unsetMapTool(self) def deactivate(self): self.rubber.reset() lineLayer = QgsMapLayerRegistry.instance().mapLayer(self.layerId) if lineLayer is not None: lineLayer.layerDeleted.disconnect(self.unsetMapTool) QgsMapTool.deactivate(self) def canvasMoveEvent(self, mouseEvent): # put the observations within tolerance in the rubber band self.rubber.reset() for f in self.getFeatures(mouseEvent.pos()): self.rubber.addGeometry(f.geometry(), None) def canvasPressEvent(self, mouseEvent): pos = mouseEvent.pos() observations = self.getFeatures(pos) point = self.toMapCoordinates(pos) self.doIntersection(point, observations) def getFeatures(self, pixPoint): snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers([self.snapLayer]) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) # output snapped features features = [] alreadyGot = [] for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if featureId not in alreadyGot: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid( featureId)).nextFeature(f) is not False: features.append(QgsFeature(f)) alreadyGot.append(featureId) return features def doIntersection(self, initPoint, observations): nObs = len(observations) if nObs < 2: return self.rubber.reset() self.dlg = IntersectionDialog(self.iface, observations, initPoint) if not self.dlg.exec_() or self.dlg.solution is None: return intersectedPoint = self.dlg.solution self.saveIntersectionResult(self.dlg.report, intersectedPoint) self.saveDimension(intersectedPoint, self.dlg.observations) def saveIntersectionResult(self, report, intersectedPoint): # save the intersection result (point) and its report # check first while True: if not self.settings.value("advancedIntersectionWritePoint"): break # if we do not place any point, skip layerid = self.settings.value("advancedIntersectionLayer") message = QCoreApplication.translate( "IntersectIt", "To place the intersection solution," " you must select a layer in the settings.") status, intLayer = self.checkLayerExists(layerid, message) if status == 2: continue if status == 3: return if self.settings.value("advancedIntersectionWriteReport"): reportField = self.settings.value("reportField") message = QCoreApplication.translate( "IntersectIt", "To save the intersection report, please select a field for it." ) status = self.checkFieldExists(intLayer, reportField, message) if status == 2: continue if status == 3: return break # save the intersection results if self.settings.value("advancedIntersectionWritePoint"): f = QgsFeature() f.setGeometry(QgsGeometry().fromPoint(intersectedPoint)) if self.settings.value("advancedIntersectionWriteReport"): irep = intLayer.dataProvider().fieldNameIndex(reportField) f.addAttribute(irep, report) intLayer.dataProvider().addFeatures([f]) intLayer.updateExtents() self.mapCanvas.refresh() def saveDimension(self, intersectedPoint, observations): # check that dimension layer and fields have been set correctly if not self.settings.value( "dimensionDistanceWrite") and not self.settings.value( "dimensionOrientationWrite"): return # if we do not place any dimension, skip obsTypes = ("Distance", "Orientation") recheck = True while recheck: # settings might change during checking, # so recheck both observation types whenever the settings dialog is shown recheck = False for obsType in obsTypes: while True: if not self.settings.value("dimension" + obsType + "Write"): break # check layer layerId = self.settings.value("dimension" + obsType + "Layer") message = QCoreApplication.translate( "IntersectIt", "To place dimensions, " "you must define a layer in the settings.") status, dimLayer = self.checkLayerExists(layerId, message) if status == 2: recheck = True continue if status == 3: return # check fields if self.settings.value("dimension" + obsType + "ObservationWrite"): obsField = self.settings.value("dimension" + obsType + "ObservationField") message = QCoreApplication.translate( "IntersectIt", "To save the observation in the layer," " please select a field for it.") status = self.checkFieldExists(dimLayer, obsField, message) if status == 2: recheck = True continue if status == 3: return if self.settings.value("dimension" + obsType + "PrecisionWrite"): precisionField = self.settings.value("dimension" + obsType + "PrecisionField") message = QCoreApplication.translate( "IntersectIt", "To save the precision of observation," " please select a field for it.") status = self.checkFieldExists(dimLayer, precisionField, message) if status == 2: recheck = True continue if status == 3: return break # save the intersection results for obsType in obsTypes: if self.settings.value("dimension" + obsType + "Write"): layerid = self.settings.value("dimension" + obsType + "Layer") layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if layer is None: continue initFields = layer.dataProvider().fields() features = [] for obs in observations: if obs["type"] != obsType.lower(): continue f = QgsFeature() f.setFields(initFields) f.initAttributes(initFields.size()) if self.settings.value("dimension" + obsType + "ObservationWrite"): f[self.settings.value( "dimension" + obsType + "ObservationField")] = obs["observation"] if self.settings.value("dimension" + obsType + "PrecisionWrite"): f[self.settings.value( "dimension" + obsType + "PrecisionField")] = obs["precision"] p0 = QgsPoint(obs["x"], obs["y"]) p1 = intersectedPoint if obs["type"] == "distance": geom = Arc(p0, p1).geometry() elif obs["type"] == "orientation": geom = QgsGeometry().fromPolyline([p0, p1]) else: raise NameError("Invalid observation %s" % obs["type"]) f.setGeometry(geom) features.append(QgsFeature(f)) if not layer.dataProvider().addFeatures(features): self.iface.messageBar().pushMessage( "Could not commit %s observations" % obsType, QgsMessageBar.CRITICAL) layer.updateExtents() self.mapCanvas.refresh() def checkLayerExists(self, layerid, message): # returns: # 1: layer exists # 2: does not exist, settings has been open, so loop once more (i.e. continue) # 3: does not exist, settings not edited, so cancel layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if layer is not None: return 1, layer reply = QMessageBox.question( self.iface.mainWindow(), "Intersect It", message + " Would you like to open settings?", QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if MySettingsDialog().exec_(): return 2 return 3 def checkFieldExists(self, layer, field, message): # returns: # 1: field exists # 2: does not exist, settings has been open, so loop once more (i.e. continue) # 3: does not exist, settings not edited, so cancel if layer.dataProvider().fieldNameIndex(field) != -1: return 1 reply = QMessageBox.question( self.iface.mainWindow(), "Intersect It", message + " Would you like to open settings?", QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if MySettingsDialog().exec_(): return 2 return 3
class SwissLocatorFilter(QgsLocatorFilter): HEADERS = { b'User-Agent': b'Mozilla/5.0 QGIS Swiss Geoportal Locator Filter' } message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget) def __init__(self, filter_type: FilterType, iface: QgisInterface = None, crs: str = None): """" :param filter_type: the type of filter :param locale_lang: the language of the locale. :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise :param crs: if iface is not given, it shall be provided, see clone() """ super().__init__() self.type = filter_type self.rubber_band = None self.feature_rubber_band = None self.iface = iface self.map_canvas = None self.settings = Settings() self.transform_ch = None self.transform_4326 = None self.map_tip = None self.current_timer = None self.crs = None self.event_loop = None self.result_found = False self.access_managers = {} self.nam_map_tip = None self.nam_fetch_feature = None if crs: self.crs = crs self.lang = get_language() self.searchable_layers = searchable_layers(self.lang, restrict=True) if iface is not None: # happens only in main thread self.map_canvas = iface.mapCanvas() self.map_canvas.destinationCrsChanged.connect( self.create_transforms) self.rubber_band = QgsRubberBand(self.map_canvas, QgsWkbTypes.PointGeometry) self.rubber_band.setColor(QColor(255, 255, 50, 200)) self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE) self.rubber_band.setIconSize(15) self.rubber_band.setWidth(4) self.rubber_band.setBrushStyle(Qt.NoBrush) self.feature_rubber_band = QgsRubberBand( self.map_canvas, QgsWkbTypes.PolygonGeometry) self.feature_rubber_band.setColor(QColor(255, 50, 50, 200)) self.feature_rubber_band.setFillColor(QColor(255, 255, 50, 160)) self.feature_rubber_band.setBrushStyle(Qt.SolidPattern) self.feature_rubber_band.setLineStyle(Qt.SolidLine) self.feature_rubber_band.setWidth(4) self.create_transforms() def name(self): return '{}_{}'.format(self.__class__.__name__, FilterType(self.type).name) def clone(self): return SwissLocatorFilter(self.type, crs=self.crs) def priority(self): return self.settings.value( '{type}_priority'.format(type=self.type.value)) def displayName(self): if self.type is FilterType.Location: return self.tr('Swiss Geoportal locations') elif self.type is FilterType.WMS: return self.tr('Swiss Geoportal / opendata.swiss WMS layers') elif self.type is FilterType.Feature: return self.tr('Swiss Geoportal features') else: raise NameError('Filter type is not valid.') def prefix(self): if self.type is FilterType.Location: return 'chl' elif self.type is FilterType.WMS: return 'chw' elif self.type is FilterType.Feature: return 'chf' else: raise NameError('Filter type is not valid.') def clearPreviousResults(self): self.rubber_band.reset(QgsWkbTypes.PointGeometry) self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry) if self.map_tip is not None: del self.map_tip self.map_tip = None if self.current_timer is not None: self.current_timer.stop() self.current_timer.deleteLater() self.current_timer = None def hasConfigWidget(self): return True def openConfigWidget(self, parent=None): dlg = ConfigDialog(parent) wid = dlg.findChild(QTabWidget, "tabWidget", Qt.FindDirectChildrenOnly) tab = wid.findChild(QWidget, self.type.value) wid.setCurrentWidget(tab) dlg.exec_() def create_transforms(self): # this should happen in the main thread self.crs = self.settings.value('crs') if self.crs == 'project': map_crs = self.map_canvas.mapSettings().destinationCrs() if map_crs.isValid(): self.crs = map_crs.authid().split(':')[1] if self.crs not in AVAILABLE_CRS: self.crs = '2056' assert self.crs in AVAILABLE_CRS src_crs_ch = QgsCoordinateReferenceSystem('EPSG:{}'.format(self.crs)) assert src_crs_ch.isValid() dst_crs = self.map_canvas.mapSettings().destinationCrs() self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs, QgsProject.instance()) src_crs_4326 = QgsCoordinateReferenceSystem('EPSG:4326') self.transform_4326 = QgsCoordinateTransform(src_crs_4326, dst_crs, QgsProject.instance()) def group_info(self, group: str) -> (str, str): groups = { 'zipcode': { 'name': self.tr('ZIP code'), 'layer': 'ch.swisstopo-vd.ortschaftenverzeichnis_plz' }, 'gg25': { 'name': self.tr('Municipal boundaries'), 'layer': 'ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill' }, 'district': { 'name': self.tr('District'), 'layer': 'ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill' }, 'kantone': { 'name': self.tr('Cantons'), 'layer': 'ch.swisstopo.swissboundaries3d-kanton-flaeche.fill' }, 'gazetteer': { 'name': self.tr('Index'), 'layer': 'ch.swisstopo.swissnames3d' }, # there is also: ch.bav.haltestellen-oev ? 'address': { 'name': self.tr('Address'), 'layer': 'ch.bfs.gebaeude_wohnungs_register' }, 'parcel': { 'name': self.tr('Parcel'), 'layer': None } } if group not in groups: self.info('Could not find group {} in dictionary'.format(group)) return None, None return groups[group]['name'], groups[group]['layer'] @staticmethod def rank2priority(rank) -> float: """ Translate the rank from geoportal to the priority of the result see https://api3.geo.admin.ch/services/sdiservices.html#search :param rank: an integer from 1 to 7 :return: the priority as a float from 0 to 1, 1 being a perfect match """ return float(-rank / 7 + 1) @staticmethod def box2geometry(box: str) -> QgsRectangle: """ Creates a rectangle from a Box definition as string :param box: the box as a string :return: the rectangle """ coords = re.findall(r'\b(\d+(?:\.\d+)?)\b', box) if len(coords) != 4: raise InvalidBox('Could not parse: {}'.format(box)) return QgsRectangle(float(coords[0]), float(coords[1]), float(coords[2]), float(coords[3])) @staticmethod def url_with_param(url, params) -> str: url = QUrl(url) q = QUrlQuery(url) for key, value in params.items(): q.addQueryItem(key, value) url.setQuery(q) return url.url() def fetchResults(self, search: str, context: QgsLocatorContext, feedback: QgsFeedback): try: self.dbg_info("start Swiss locator search...") if len(search) < 2: return if len(search) < 4 and self.type is FilterType.Feature: return self.result_found = False swisstopo_base_url = 'https://api3.geo.admin.ch/rest/services/api/SearchServer' swisstopo_base_params = { 'type': self.type.value, 'searchText': str(search), 'returnGeometry': 'true', 'lang': self.lang, 'sr': self.crs, 'limit': str( self.settings.value( '{type}_limit'.format(type=self.type.value))) # bbox Must be provided if the searchText is not. # A comma separated list of 4 coordinates representing # the bounding box on which features should be filtered (SRID: 21781). } # Locations, WMS layers if self.type is not FilterType.Feature: nam = NetworkAccessManager() feedback.canceled.connect(nam.abort) search_urls = [(swisstopo_base_url, swisstopo_base_params)] if self.settings.value('layers_include_opendataswiss' ) and self.type is FilterType.WMS: search_urls.append( ('https://opendata.swiss/api/3/action/package_search?', { 'q': 'q=WMS+%C3' + str(search) })) for (swisstopo_base_url, swisstopo_base_params) in search_urls: swisstopo_base_url = self.url_with_param( swisstopo_base_url, swisstopo_base_params) self.dbg_info(swisstopo_base_url) try: (response, content) = nam.request(swisstopo_base_url, headers=self.HEADERS, blocking=True) self.handle_response(response, search, feedback) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(err) # Feature search else: # Feature search is split in several requests # otherwise URL is too long self.access_managers = {} try: layers = list(self.searchable_layers.keys()) assert len(layers) > 0 step = 30 for l in range(0, len(layers), step): last = min(l + step - 1, len(layers) - 1) swisstopo_base_params['features'] = ','.join( layers[l:last]) self.access_managers[self.url_with_param( swisstopo_base_url, swisstopo_base_params)] = None except IOError: self.info( 'Layers data file not found. Please report an issue.', Qgis.Critical) # init event loop # wait for all requests to end self.event_loop = QEventLoop() def reply_finished(response): self.handle_response(response, search, feedback) if response.url in self.access_managers: self.access_managers[response.url] = None for nam in self.access_managers.values(): if nam is not None: return self.event_loop.quit() feedback.canceled.connect(self.event_loop.quit) # init the network access managers, create the URL for swisstopo_base_url in self.access_managers: self.dbg_info(swisstopo_base_url) nam = NetworkAccessManager() self.access_managers[swisstopo_base_url] = nam nam.finished.connect(reply_finished) nam.request(swisstopo_base_url, headers=self.HEADERS, blocking=False) feedback.canceled.connect(nam.abort) # Let the requests end and catch all exceptions (and clean up requests) if len(self.access_managers) > 0: try: self.event_loop.exec_( QEventLoop.ExcludeUserInputEvents) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(str(err)) if not self.result_found: result = QgsLocatorResult() result.filter = self result.displayString = self.tr('No result found.') result.userData = NoResult().as_definition() self.resultFetched.emit(result) except Exception as e: self.info(e, Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] self.info( '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) self.info( traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def handle_response(self, response, search: str, feedback: QgsFeedback): try: if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info( "Error in main response with status code: {} from {}". format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) # self.dbg_info(data) if self.is_opendata_swiss_response(data): visited_capabilities = [] for loc in data['result']['results']: display_name = loc['title'].get(self.lang, "") if not display_name: # Fallback to german display_name = loc['title']['de'] for res in loc['resources']: url = res['url'] url_components = urlparse(url) wms_url = url_components.scheme + '://' + url_components.netloc + '/' + url_components.path + '?' result = QgsLocatorResult() result.filter = self result.group = 'opendata.swiss' result.icon = QgsApplication.getThemeIcon( "/mActionAddWmsLayer.svg") if 'wms' in url.lower(): if res['media_type'] == 'WMS': result.displayString = display_name result.description = url if res['title']['de'] == 'GetMap': layers = parse_qs( url_components.query)['LAYERS'] result.userData = WMSLayerResult( layer=layers[0], title=display_name, url=wms_url).as_definition() self.result_found = True self.resultFetched.emit(result) elif 'request=getcapabilities' in url.lower( ) and url_components.netloc not in visited_capabilities: visited_capabilities.append( url_components.netloc) def parse_capabilities_result(response): capabilities = ET.fromstring( response.content) # Get xml namespace match = re.match(r'\{.*\}', capabilities.tag) namespace = match.group(0) if match else '' # Search for layers containing the search term in the name or title for layer in capabilities.findall( './/{}Layer'.format(namespace)): layername = self.find_text( layer, '{}Name'.format(namespace)) layertitle = self.find_text( layer, '{}Title'.format(namespace)) if layername and ( search in layername.lower() or search in layertitle.lower()): if not layertitle: layertitle = layername result.displayString = layertitle result.description = '{}?LAYERS={}'.format( url.replace( 'GetCapabilities', 'GetMap'), layername) result.userData = WMSLayerResult( layer=layername, title=layertitle, url=wms_url).as_definition() self.result_found = True self.resultFetched.emit(result) self.event_loop.quit() # Retrieve Capabilities xml self.event_loop = QEventLoop() nam = NetworkAccessManager() nam.finished.connect(parse_capabilities_result) nam.request(url, headers=self.HEADERS, blocking=False) feedback.canceled.connect(self.event_loop.quit) try: self.event_loop.exec_( QEventLoop.ExcludeUserInputEvents) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(err) else: for loc in data['results']: self.dbg_info("keys: {}".format(loc['attrs'].keys())) result = QgsLocatorResult() result.filter = self result.group = 'Swiss Geoportal' if loc['attrs']['origin'] == 'layer': # available keys: ['origin', 'lang', 'layer', 'staging', 'title', 'topics', 'detail', 'label', 'id'] for key, val in loc['attrs'].items(): self.dbg_info('{}: {}'.format(key, val)) result.displayString = loc['attrs']['title'] result.description = loc['attrs']['layer'] result.userData = WMSLayerResult( layer=loc['attrs']['layer'], title=loc['attrs']['title'], url='http://wms.geo.admin.ch/?VERSION%3D2.0.0' ).as_definition() result.icon = QgsApplication.getThemeIcon( "/mActionAddWmsLayer.svg") self.result_found = True self.resultFetched.emit(result) elif loc['attrs']['origin'] == 'feature': for key, val in loc['attrs'].items(): self.dbg_info('{}: {}'.format(key, val)) layer = loc['attrs']['layer'] point = QgsPointXY(loc['attrs']['lon'], loc['attrs']['lat']) if layer in self.searchable_layers: layer_display = self.searchable_layers[layer] else: self.info( self. tr('Layer {} is not in the list of searchable layers.' ' Please report issue.'.format(layer)), Qgis.Warning) layer_display = layer result.group = layer_display result.displayString = loc['attrs']['detail'] result.userData = FeatureResult( point=point, layer=layer, feature_id=loc['attrs'] ['feature_id']).as_definition() result.icon = QIcon( ":/plugins/swiss_locator/icons/swiss_locator.png") self.result_found = True self.resultFetched.emit(result) else: # locations for key, val in loc['attrs'].items(): self.dbg_info('{}: {}'.format(key, val)) group_name, group_layer = self.group_info( loc['attrs']['origin']) if 'layerBodId' in loc['attrs']: self.dbg_info("layer: {}".format( loc['attrs']['layerBodId'])) if 'featureId' in loc['attrs']: self.dbg_info("feature: {}".format( loc['attrs']['featureId'])) result.displayString = strip_tags( loc['attrs']['label']) # result.description = loc['attrs']['detail'] # if 'featureId' in loc['attrs']: # result.description = loc['attrs']['featureId'] result.group = group_name result.userData = LocationResult( point=QgsPointXY(loc['attrs']['y'], loc['attrs']['x']), bbox=self.box2geometry( loc['attrs']['geom_st_box2d']), layer=group_layer, feature_id=loc['attrs']['featureId'] if 'featureId' in loc['attrs'] else None, html_label=loc['attrs']['label']).as_definition() result.icon = QIcon( ":/plugins/swiss_locator/icons/swiss_locator.png") self.result_found = True self.resultFetched.emit(result) except Exception as e: self.info(str(e), Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] self.info( '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) self.info( traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def triggerResult(self, result: QgsLocatorResult): # this should be run in the main thread, i.e. mapCanvas should not be None # remove any map tip self.clearPreviousResults() user_data = NoResult try: swiss_result = result_from_data(result) except SystemError: self.message_emitted.emit( self.displayName(), self. tr('QGIS Swiss Locator encountered an error. Please <b>update to QGIS 3.16.2</b> or newer.' ), Qgis.Warning, None) if type(swiss_result) == NoResult: return # WMS if type(swiss_result) == WMSLayerResult: url_with_params = 'contextualWMSLegend=0' \ '&crs=EPSG:{crs}' \ '&dpiMode=7' \ '&featureCount=10' \ '&format=image/png' \ '&layers={layer}' \ '&styles=' \ '&url={url}' \ .format(crs=self.crs, layer=swiss_result.layer, url=swiss_result.url) wms_layer = QgsRasterLayer(url_with_params, result.displayString, 'wms') label = QLabel() label.setTextFormat(Qt.RichText) label.setTextInteractionFlags(Qt.TextBrowserInteraction) label.setOpenExternalLinks(True) if 'geo.admin.ch' in swiss_result.url.lower(): label.setText( '<a href="https://map.geo.admin.ch/' '?lang={}&bgLayer=ch.swisstopo.pixelkarte-farbe&layers={}">' 'Open layer in map.geo.admin.ch</a>'.format( self.lang, swiss_result.layer)) if not wms_layer.isValid(): msg = self.tr('Cannot load WMS layer: {} ({})'.format( swiss_result.title, swiss_result.layer)) level = Qgis.Warning self.info(msg, level) else: msg = self.tr('WMS layer added to the map: {} ({})'.format( swiss_result.title, swiss_result.layer)) level = Qgis.Info QgsProject.instance().addMapLayer(wms_layer) self.message_emitted.emit(self.displayName(), msg, level, label) # Feature elif type(swiss_result) == FeatureResult: point = QgsGeometry.fromPointXY(swiss_result.point) point.transform(self.transform_4326) self.highlight(point) if self.settings.value('show_map_tip'): self.show_map_tip(swiss_result.layer, swiss_result.feature_id, point) # Location else: point = QgsGeometry.fromPointXY(swiss_result.point) if swiss_result.bbox.isNull(): bbox = None else: bbox = QgsGeometry.fromRect(swiss_result.bbox) bbox.transform(self.transform_ch) layer = swiss_result.layer feature_id = swiss_result.feature_id if not point: return point.transform(self.transform_ch) self.highlight(point, bbox) if layer and feature_id: self.fetch_feature(layer, feature_id) if self.settings.value('show_map_tip'): self.show_map_tip(layer, feature_id, point) else: self.current_timer = QTimer() self.current_timer.timeout.connect(self.clearPreviousResults) self.current_timer.setSingleShot(True) self.current_timer.start(5000) def highlight(self, point, bbox=None): if bbox is None: bbox = point self.rubber_band.reset(QgsWkbTypes.PointGeometry) self.rubber_band.addGeometry(point, None) rect = bbox.boundingBox() rect.scale(1.1) self.map_canvas.setExtent(rect) self.map_canvas.refresh() def fetch_feature(self, layer, feature_id): # Try to get more info self.nam_fetch_feature = NetworkAccessManager() url_detail = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}' \ .format(layer=layer, feature_id=feature_id) params = {'lang': self.lang, 'sr': self.crs} url_detail = self.url_with_param(url_detail, params) self.dbg_info(url_detail) self.nam_fetch_feature.finished.connect(self.parse_feature_response) self.nam_fetch_feature.request(url_detail, headers=self.HEADERS, blocking=False) def parse_feature_response(self, response): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info( "Error in feature response with status code: {} from {}". format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) self.dbg_info(data) if 'feature' not in data or 'geometry' not in data['feature']: return if 'rings' in data['feature']['geometry']: rings = data['feature']['geometry']['rings'] self.dbg_info(rings) for r in range(0, len(rings)): for p in range(0, len(rings[r])): rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1]) geometry = QgsGeometry.fromPolygonXY(rings) geometry.transform(self.transform_ch) self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry) self.feature_rubber_band.addGeometry(geometry, None) def show_map_tip(self, layer, feature_id, point): if layer and feature_id: url_html = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}/htmlPopup' \ .format(layer=layer, feature_id=feature_id) params = {'lang': self.lang, 'sr': self.crs} url_html = self.url_with_param(url_html, params) self.dbg_info(url_html) self.nam_map_tip = NetworkAccessManager() self.nam_map_tip.finished.connect( lambda response: self.parse_map_tip_response(response, point)) self.nam_map_tip.request(url_html, headers=self.HEADERS, blocking=False) def parse_map_tip_response(self, response, point): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info( "Error in map tip response with status code: {} from {}". format(response.status_code, response.url)) return self.dbg_info(response.content.decode('utf-8')) self.map_tip = MapTip(self.iface, response.content.decode('utf-8'), point.asPoint()) self.map_tip.closed.connect(self.clearPreviousResults) def info(self, msg="", level=Qgis.Info): self.logMessage(str(msg), level) def dbg_info(self, msg=""): if DEBUG: self.info(msg) @staticmethod def break_camelcase(identifier): matches = re.finditer( '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', identifier) return ' '.join([m.group(0) for m in matches]) def is_opendata_swiss_response(self, json): return 'opendata.swiss' in json.get("help", []) def find_text(self, xmlElement, match): node = xmlElement.find(match) return node.text if node is not None else ''
class PlanetSearchResultsView(QTreeView): """ """ checkedCountChanged = pyqtSignal(int) def __init__(self, parent, iface=None, api_key=None, request_type=None, request=None, response_timeout=RESPONSE_TIMEOUT, sort_order=None): super().__init__(parent=parent) # noinspection PyTypeChecker self._parent: PlanetSearchResultsWidget = parent self._iface = iface self._api_key = api_key self._request_type = request_type self._request = request self._response_timeout = response_timeout self._sort_order = sort_order self._footprint = None self._setup_footprint() self._thumb_cache_dir: str = pluginSetting( 'thumbCachePath', namespace=SETTINGS_NAMESPACE) self._checked_count = 0 self._checked_queue = {} self._search_model = PlanetSearchResultsModel( parent=self, api_key=api_key, request_type=request_type, request=request, thumb_cache_dir=self._thumb_cache_dir, sort_order=self._sort_order ) # Generic model, as background, until results come in # self._search_model = QStandardItemModel(0, 2, self) self._search_model.thumbnail_cache().set_job_subclass( PlanetQgisRenderJob ) p = self.palette() p.setColor(QPalette.Highlight, ITEM_BACKGROUND_COLOR) self.setPalette(p) self.setModel(self._search_model) self.setIconSize(QSize(48, 48)) self.setAlternatingRowColors(True) self.setHeaderHidden(False) # self.setColumnWidth(0, 250) self.setColumnWidth(1, 26) self.setIndentation(int(self.indentation() * 0.75)) hv = self.header() hv.setStretchLastSection(False) hv.setSectionResizeMode(0, QHeaderView.Stretch) hv.setSectionResizeMode(1, QHeaderView.Fixed) if len(sort_order) > 1: if sort_order[1] == 'asc': sort_indicator = Qt.AscendingOrder else: sort_indicator = Qt.DescendingOrder hv.setSortIndicator(0, sort_indicator) hv.setSortIndicatorShown(True) self.viewport().setAttribute(Qt.WA_Hover) self.setMouseTracking(True) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) # self.setWordWrap(True) # self.setTextElideMode(Qt.ElideNone) self.item_delegate = PlanetNodeItemDelegate(parent=self) # noinspection PyUnresolvedReferences self.item_delegate.previewFootprint['PyQt_PyObject'].connect( self.preview_footprint) # noinspection PyUnresolvedReferences self.item_delegate.clearFootprint.connect(self.clear_footprint) self.setItemDelegateForColumn(0, self.item_delegate) self.act_delegate = PlanetNodeActionDelegate(parent=self) self.setItemDelegateForColumn(1, self.act_delegate) self.setContextMenuPolicy(Qt.CustomContextMenu) # noinspection PyUnresolvedReferences self.customContextMenuRequested['QPoint'].connect(self.open_menu) # noinspection PyUnresolvedReferences self.clicked['QModelIndex'].connect(self.item_clicked) # noinspection PyUnresolvedReferences self.expanded['QModelIndex'].connect(self.item_expanded) def search_model(self): return self._search_model def checked_count(self): return self._checked_count def checked_queue(self): return self._checked_queue def _setup_footprint(self): if self._iface: log.debug('iface is available, adding footprint support') self._footprint = QgsRubberBand( self._iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self._footprint.setFillColor(QColor(255, 255, 255, 10)) self._footprint.setStrokeColor(PLANET_COLOR) self._footprint.setWidth(2) else: log.debug('iface is None, skipping footprint support') self._footprint = None # noinspection PyMethodMayBeStatic def qgsfeature_feature_from_node(self, node: PlanetNode): # TODO: Resolve geometry by node_type or do that under node.geometry()? # geom = None # if node.node_type() == NodeT.DAILY_SCENE_IMAGE: # geom = node.geometry() # TODO: Add node # feature_collect = { # 'type': 'FeatureCollection', # 'features': [ # { # 'type': 'Feature', # 'geometry': node.geometry(), # 'properties': { # 'id': node.item_id() # } # } # ] # } feature_collect = { 'type': 'FeatureCollection', 'features': [ node.resource() ] } feature_collect_json = json.dumps(feature_collect) # noinspection PyUnusedLocal features = [] # noinspection PyBroadException try: utf8 = QTextCodec.codecForName('UTF-8') # TODO: Add node id, properties as fields? fields = QgsFields() features = QgsJsonUtils().stringToFeatureList( string=feature_collect_json, fields=fields, encoding=utf8) except Exception: log.debug('Footprint GeoJSON could not be parsed') return if not len(features) > 0: log.debug('GeoJSON parsing created no features') return return features[0] @pyqtSlot() def clear_footprint(self): if self._footprint: self._footprint.reset(QgsWkbTypes.PolygonGeometry) @pyqtSlot('PyQt_PyObject') def preview_footprint(self, node: PlanetNode): if not self._footprint: if LOG_VERBOSE: log.debug('Footprint is None, skipping footprint preview') return if node.has_group_footprint(): geoms = node.geometries() else: geoms = [node.geometry()] self.clear_footprint() qgs_geoms = [qgsgeometry_from_geojson(g) for g in geoms] for qgs_geom in qgs_geoms: self._footprint.addGeometry( qgs_geom, QgsCoordinateReferenceSystem("EPSG:4326") ) if LOG_VERBOSE: log.debug('Footprint sent to canvas') @pyqtSlot(list) def zoom_to_footprint(self, nodes: [PlanetNode]): skip = 'skipping zoom to footprint' if not self._footprint: log.debug(f'Footprint is None, {skip}') return if len(nodes) < 1: log.debug('No nodes available, skipping zoom to footprint') return first_node = nodes[0] if first_node.has_group_footprint(): json_geoms = first_node.geometries() else: json_geoms = [node.geometry() for node in nodes] qgs_geoms: [QgsGeometry] = \ [qgsgeometry_from_geojson(j) for j in json_geoms] if len(qgs_geoms) < 1: log.debug(f'Geometry collection empty, {skip}') return rect_geoms: QgsRectangle = qgs_geoms[0].boundingBox() for i in range(len(qgs_geoms)): if i == 0: continue r: QgsRectangle = qgs_geoms[i].boundingBox() rect_geoms.combineExtentWith(r) if rect_geoms.isNull(): log.debug(f'Footprint geometry is null, {skip}') return # noinspection PyArgumentList transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance().crs(), QgsProject.instance() ) rect_footprint: QgsRectangle = \ transform.transformBoundingBox(rect_geoms) if not rect_footprint.isEmpty(): rect_footprint.scale(1.05) self._iface.mapCanvas().setExtent(rect_footprint) self._iface.mapCanvas().refresh() @pyqtSlot('PyQt_PyObject') def preview_thumbnail( self, node, name=PE_PREVIEW, remove_existing=True): item_name_geo = f'{node.item_type_id_key()}{THUMB_GEO}' item_geo_path = os.path.join( self.search_model().thumbnail_cache().cache_dir(), f'{item_name_geo}{THUMB_EXT}') if not preview_local_item_raster( item_geo_path, name, remove_existing=remove_existing): log.warning(f'Item preview {item_name_geo} failed to load') @pyqtSlot(dict) def update_preview_thumbnails(self, selected_nodes): group = temp_preview_group() tree_node_names: List[str] = \ [t_node.name() for t_node in group.children()] for name in tree_node_names: if name.endswith('_thumb'): name_sans = name.replace('_thumb', '') if name_sans not in selected_nodes: remove_maplayers_by_name(name, only_first=True) if name_sans in selected_nodes: # Keep already loaded layer, skip reloading it selected_nodes[name_sans] = None for _, node in selected_nodes.items(): if node is not None: self.preview_thumbnail(node) # for node in deselected_nodes: # if f'{node.item_type_id_key()}_thumb'.lower() in node_names: # remove_maplayers_by_name( # f'{node.item_type_id_key()}_thumb'.lower(), # only_first=True) # # node_names = [t_node.name() for t_node in group.children()] # for node in selected_nodes: # if f'{node.item_type_id_key()}_thumb'.lower() not in node_names: # self.preview_thumbnail(node) @pyqtSlot(list) def add_preview_groups(self, nodes: List[PlanetNode]): if len(nodes) < 1 or not isinstance(nodes[0], PlanetNode): log.debug('No nodes found to add to preview group') return if nodes[0].is_group_node(): log.debug('Adding preview group for group') # Grouping tree items are only singularly passed self.add_preview_group_for_group(nodes[0]) else: log.debug('Adding preview group for items') self.add_preview_groups_for_items(nodes) @pyqtSlot(list) def add_preview_group_for_group(self, node: PlanetNode): name = None child_node_type = None if node.node_type() == NodeT.DAILY_SCENE: child_node_type = NodeT.DAILY_SCENE_IMAGE item_type = node.name() or '' title = ['Daily', item_type, 'Scene'] name = f'{" ".join(title)} ' \ f'{node.formatted_date_time(node.sort_date())}' elif node.node_type() == NodeT.DAILY_SAT_GROUP: child_node_type = NodeT.DAILY_SCENE_IMAGE item_type = node.parent().name() or '' title = ['Daily', item_type, f'Satellite {node.name()} Group'] name = f'{" ".join(title)} ' \ f'{node.formatted_date_time(node.sort_date())}' if child_node_type is None: log.debug('No node type found for tree group searching') return item_nodes = node.children_of_node_type(child_node_type) if item_nodes: create_preview_group( name, item_nodes, self._search_model.p_client().api_key(), tile_service='xyz', search_query=self._request, sort_order=self._sort_order ) else: log.debug(f"No items found for node type '{child_node_type.name}' " f"in tree group '{name}'") @pyqtSlot(list) def add_preview_groups_for_items(self, nodes: List[PlanetNode]) -> None: prev_types = [] # maintain some sort order prev_type_nodes = {} for node in nodes: item_type = node.item_type() if item_type not in prev_types: prev_types.append(item_type) prev_type_nodes[item_type] = [] prev_type_nodes[item_type].append(node) for prev_type in sorted(prev_types): prev_nodes: List[PlanetNode] = prev_type_nodes[prev_type] # if prev_type in DAILY_ITEM_TYPES_DICT: # # Group imagery by type # item_keys = [n.item_type_id() for n in prev_nodes] # tile_url = self._search_model.p_client().get_tile_url( # item_keys) # create_preview_group(prev_type, prev_nodes, tile_url) # else: # # For groups, use any item type listing # for prev_node in prev_nodes: # item_keys = prev_node.item_type_id_list() # if item_keys: # tile_url = \ # self._search_model.p_client().get_tile_url( # item_keys) # create_preview_group(prev_type, [], tile_url) create_preview_group( prev_type, prev_nodes, self._search_model.p_client().api_key(), tile_service='xyz', search_query=self._request, sort_order=self._sort_order ) @pyqtSlot(list) def copy_ids_to_clipboard(self, nodes): node_ids = [n.item_type_id() for n in nodes if n.item_id()] if node_ids: cb = QgsApplication.clipboard() cb.setText(','.join(node_ids)) @pyqtSlot('QPoint') def open_menu(self, pos): """ :type pos: QPoint :return: """ index = self.indexAt(pos) node: PlanetNode = self.model().get_node(index) if (node.node_type() == NodeT.LOAD_MORE and node.parent() == self.model().root): return menu = QMenu() # Single, current Item's index add_menu_section_action('Current item', menu) if node.has_footprint() or node.has_group_footprint(): zoom_fp_act = QAction('Zoom to footprint', menu) # noinspection PyUnresolvedReferences zoom_fp_act.triggered[bool].connect( lambda: self.zoom_to_footprint([node])) menu.addAction(zoom_fp_act) if node.can_load_preview_layer(): prev_layer_act = QAction('Add preview layer to map', menu) # noinspection PyUnresolvedReferences prev_layer_act.triggered[bool].connect( lambda: self.add_preview_groups([node])) if node.child_images_count() > CHILD_COUNT_THRESHOLD_FOR_PREVIEW: prev_layer_act.setEnabled(False) prev_layer_act.setToolTip("The node contains too many images to preview") menu.setToolTipsVisible(True) menu.addAction(prev_layer_act) if node.item_id() and node.has_resource(): copy_id_act = QAction('Copy ID to clipboard', menu) # noinspection PyUnresolvedReferences copy_id_act.triggered[bool].connect( lambda: self.copy_ids_to_clipboard([node])) menu.addAction(copy_id_act) # Selected Items sel_model = self.selectionModel() model = self.model() # Ensure to grab only first column of indexes (or will get duplicates) all_nodes = [model.get_node(i) for i in sel_model.selectedIndexes() if i.column() == 0] log.debug(f'Selected items: {len(all_nodes)}') if len(all_nodes) == 1 and all_nodes[0] == node: menu.exec_(self.viewport().mapToGlobal(pos)) return nodes_have_footprints = \ [node for node in all_nodes if node.has_footprint()] nodes_w_ids = \ [node for node in all_nodes if node.item_id() and node.has_resource()] nodes_can_prev = \ [node for node in all_nodes if node.can_load_preview_layer() and node.has_resource()] if any([nodes_have_footprints, nodes_w_ids, nodes_can_prev]): add_menu_section_action(f'Selected images', menu) if nodes_have_footprints: zoom_fps_act = QAction( f'Zoom to total footprint ' f'({len(nodes_have_footprints)} items)', menu) # noinspection PyUnresolvedReferences zoom_fps_act.triggered[bool].connect( lambda: self.zoom_to_footprint(nodes_have_footprints)) menu.addAction(zoom_fps_act) if nodes_can_prev: prev_layers_act = QAction( f'Add preview layer to map ' f'({len(nodes_can_prev)} items)', menu) # noinspection PyUnresolvedReferences prev_layers_act.triggered[bool].connect( lambda: self.add_preview_groups(nodes_can_prev)) menu.addAction(prev_layers_act) if nodes_w_ids: copy_ids_act = QAction( f'Copy IDs to clipboard ({len(nodes_w_ids)} items)', menu) # noinspection PyUnresolvedReferences copy_ids_act.triggered[bool].connect( lambda: self.copy_ids_to_clipboard(nodes_w_ids)) menu.addAction(copy_ids_act) menu.exec_(self.viewport().mapToGlobal(pos)) @pyqtSlot('QModelIndex') def item_clicked(self, index): node: PlanetNode = self.model().get_node(index) log.debug(f'Index clicked: row {index.row()}, col {index.column()}, ' f'{node.item_type_id()}') if index.column() == 0: if (node.node_type() == NodeT.LOAD_MORE and node.parent() == self.model().root): self.model().fetch_more_top_items(index) elif index.column() == 1: self.open_menu(self.viewport().mapFromGlobal(QCursor.pos())) @pyqtSlot('QModelIndex') def item_expanded(self, index: QModelIndex): node: PlanetNode = self.model().get_node(index) log.debug(f'Index expanded: row {index.row()}, col {index.column()}, ' f'{node.item_type_id()}') # log.debug( # f'Node traversed: {node.is_traversed()} {node.item_type_id()}') # if index.column() == 0: # if (node.node_type() == NodeT.DAILY_SCENE # and not node.is_traversed()): # log.debug( # f'Traversing node: {node.item_type_id()}') # for sat_grp in node.children(): # sat_grp: PlanetNode # self.expand(sat_grp.index()) # # for image in sat_grp.children(): # if image.has_thumbnail(): # self.search_model().add_to_thumb_queue( # image.item_type_id_key(), image.index()) # self.search_model().fetch_thumbnail(image) # # node.set_is_traversed(True) def _update_checked_queue(self, checked_nodes: Set[PlanetNode], unchecked_nodes: Set[PlanetNode]): for c_node in checked_nodes: it_id = c_node.item_type_id() self._checked_queue[it_id] = c_node for u_node in unchecked_nodes: it_id = u_node.item_type_id() if it_id in self._checked_queue: del self._checked_queue[it_id] self._checked_count = len(self._checked_queue) log.debug(f'checked_count: {self._checked_count}') if LOG_VERBOSE: sorted_item_ids = sorted(self._checked_queue.keys()) nl = '\n' log.debug(f'checked_queue:\n' f' {"{0} ".format(nl).join(sorted_item_ids)}') # When using with {'item_type': set(nodes)} # for it_id in self._checked_queue: # log.debug(f'\n - {it_id}: ' # f'{len(self._checked_queue[it_id])}\n') # # # Super verbose output... # nl = '\n' # i_types = \ # [n.item_id() for n in self._checked_queue[it_id]] # log.debug(f'\n - {it_id}: ' # f'{len(self._checked_queue[it_id])}\n' # f' - {"{0} - ".format(nl).join(i_types)}') self.checkedCountChanged.emit(self._checked_count) def event(self, event: QEvent) -> bool: if event.type() == QEvent.Leave: if self._iface: self.clear_footprint() event.accept() return QTreeView.event(self, event) def mousePressEvent(self, event: QMouseEvent) -> None: index = self.indexAt(event.pos()) sel_model: QItemSelectionModel = self.selectionModel() if (index.column() == 1 and event.button() == Qt.LeftButton and sel_model.isSelected(index)): log.debug('Ignoring mouse press') return return QTreeView.mousePressEvent(self, event) def mouseReleaseEvent(self, event: QMouseEvent) -> None: index = self.indexAt(event.pos()) sel_model: QItemSelectionModel = self.selectionModel() if (index.column() == 1 and event.button() == Qt.LeftButton and sel_model.isSelected(index)): log.debug('Swapping left button for right, on release') self.open_menu(event.pos()) return return QTreeView.mouseReleaseEvent(self, event) def keyReleaseEvent(self, event: QKeyEvent) -> None: index = self.currentIndex() if (index.column() == 0 and event.key() in [Qt.Key_Enter, Qt.Key_Return]): node: PlanetNode = self.model().get_node(index) if (node.node_type() == NodeT.LOAD_MORE and node.parent() == self.model().root): self.model().fetch_more_top_items(index) return QTreeView.keyReleaseEvent(self, event) def dataChanged(self, t_l: QModelIndex, b_r: QModelIndex, roles: Optional[List[int]] = None) -> None: # This may need to go at end of slot super().dataChanged(t_l, b_r, roles=roles) # Note: empty roles indicates *everything* has changed, not nothing if roles is not None and (roles == [] or Qt.CheckStateRole in roles): if LOG_VERBOSE: log.debug('Node (un)checked') checked = set() unchecked = set() def update_queues(indx): node: PlanetNode = self._search_model.get_node(indx) if node.is_base_image() and node.can_be_downloaded(): if node.checked_state() == Qt.Checked: checked.add(node) elif node.checked_state() == Qt.Unchecked: unchecked.add(node) # Note: if parent of t_l and b_r differ, we ignore undefined input if t_l == b_r: if LOG_VERBOSE: log.debug('Nodes (un)checked is single') update_queues(t_l) elif t_l.parent() == b_r.parent(): if LOG_VERBOSE: log.debug('Nodes (un)checked have same parent') parent = t_l.parent() col = t_l.column() for row in range(b_r.row(), t_l.row() + 1): m_indx = self._search_model.index(row, col, parent=parent) update_queues(m_indx) if checked or unchecked: if LOG_VERBOSE: log.debug(f'Nodes checked: {checked}') log.debug(f'Nodes unchecked: {unchecked}') self._update_checked_queue(checked, unchecked) # This works, but is disabled until thumbnail georeferencing works OK # def currentChanged(self, current: QModelIndex, # previous: QModelIndex) -> None: # node: PlanetNode = self.model().get_node(current) # log.debug(f'Current item: row {current.row()}, ' # f'col {current.column()},' # f' {node.item_id()}') # if current.column() == 0: # if node.has_thumbnail() and node.thumbnail_loaded(): # self.preview_thumbnail(node) # else: # clear_local_item_raster_preview() # # return QTreeView.currentChanged(self, current, previous) # def selectionChanged(self, selected: QItemSelection, # deselected: QItemSelection) -> None: # selected_nodes = {} # for si in selected.indexes(): # if si.column() == 0: # si_node = self._search_model.get_node(si) # selected_nodes[si_node.item_type_id_key()] = si_node # # deselected_nodes = {} # for di in deselected.indexes(): # if di.column() == 0: # di_node = self._search_model.get_node(di) # deselected_nodes[di_node.item_type_id_key()] = di_node # # self.update_preview_thumbnails(selected_nodes) # # return QTreeView.selectionChanged(self, selected, deselected) @pyqtSlot() def clean_up(self): self.clear_footprint()
class MultiLayerSelection(QgsMapTool): finished = QtCore.pyqtSignal(list) def __init__(self, canvas, iface): """ Tool Behaviours: (all behaviours start edition, except for rectangle one) 1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. The selection is done with the following priority: Point, Line then Polygon. Selection is only done in visible layer. 2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1. 3- Right Click: Opens feature form 4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition follows priority of item 1; 5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangl'e are added to selection """ self.iface = iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.hoverRubberBand = QgsRubberBand(self.canvas, QGis.Polygon) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.hoverRubberBand.setColor(QColor(255, 0, 0, 90)) self.rubberBand.setWidth(1) self.reset() self.blackList = self.getBlackList() self.cursorChanged = False self.cursorChangingHotkey = QtCore.Qt.Key_Alt self.menuHovered = False # indicates hovering actions over context menu def keyPressEvent(self, e): """ Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (Alt). """ if e.key() == self.cursorChangingHotkey and not self.cursorChanged: self.cursorChanged = True QtGui.QApplication.setOverrideCursor(QCursor( Qt.PointingHandCursor)) else: self.cursorChanged = False QtGui.QApplication.restoreOverrideCursor() def getBlackList(self): settings = QSettings() settings.beginGroup('PythonPlugins/DsgTools/Options') valueList = settings.value('valueList') if valueList: valueList = valueList.split(';') return valueList else: return ['moldura'] def reset(self): """ Resets rubber band. """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QGis.Polygon) def canvasMoveEvent(self, e): """ Used only on rectangle select. """ if self.menuHovered: # deactivates rubberband when the context menu is "destroyed" self.hoverRubberBand.reset(QGis.Polygon) if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Builds rubberband rect. """ self.rubberBand.reset(QGis.Polygon) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """ Builds rectangle from self.startPoint and self.endPoint """ if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setAction(self, action): self.toolAction = action self.toolAction.setCheckable(True) def canvasReleaseEvent(self, e): """ After the rectangle is built, here features are selected. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: firstGeom = self.checkSelectedLayers() self.isEmittingPoint = False r = self.rectangle() if r is None: return layers = self.canvas.layers() for layer in layers: #ignore layers on black list and features that are not vector layers if not isinstance(layer, QgsVectorLayer) or ( self.layerHasPartInBlackList(layer.name())): continue if firstGeom is not None and layer.geometryType() != firstGeom: # if there are features already selected, shift will only get the same type geometry # if more than one ty of geometry is present, only the strongest will be selected continue #builds bbRect and select from layer, adding selection bbRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, r) layer.select(bbRect, True) self.rubberBand.hide() def canvasPressEvent(self, e): """ Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = True self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) else: self.isEmittingPoint = False self.createContextMenu(e) def getCursorRect(self, e): """ Calculates small cursor rectangle around mouse position. Used to facilitate operations """ p = self.toMapCoordinates(e.pos()) w = self.canvas.mapUnitsPerPixel() * 10 return QgsRectangle(p.x() - w, p.y() - w, p.x() + w, p.y() + w) def layerHasPartInBlackList(self, lyrName): """ Verifies if terms in black list appear on lyrName """ for item in self.getBlackList(): if item.lower() in lyrName.lower(): return True return False def getPrimitiveDict(self, e, hasControlModifier=False): """ Builds a dict with keys as geometryTypes of layer, which are QGis.Point (value 0), QGis.Line (value 1) or QGis.Polygon (value 2), and values as layers from self.iface.legendInterface().layers(). When self.iface.legendInterface().layers() is called, a list of layers ordered according to lyr order in TOC is returned. """ #these layers are ordered by view order primitiveDict = dict() firstGeom = self.checkSelectedLayers() for lyr in self.iface.legendInterface().layers(): #ordered layers #layer types other than VectorLayer are ignored, as well as layers in black list and layers that are not visible if (lyr.type() != QgsMapLayer.VectorLayer) or ( self.layerHasPartInBlackList(lyr.name()) ) or not self.iface.legendInterface().isLayerVisible(lyr): continue if hasControlModifier and ( not firstGeom) and (not primitiveDict.keys() or lyr.geometryType() < firstGeom): firstGeom = lyr.geometryType() geomType = lyr.geometryType() if geomType not in primitiveDict.keys(): primitiveDict[geomType] = [] #removes selection if (not hasControlModifier and e.button() == QtCore.Qt.LeftButton ) or (hasControlModifier and e.button() == QtCore.Qt.RightButton): lyr.removeSelection() primitiveDict[geomType].append(lyr) if hasControlModifier and firstGeom in [0, 1, 2]: return {firstGeom: primitiveDict[firstGeom]} else: return primitiveDict def deactivate(self): """ Deactivate tool. """ QtGui.QApplication.restoreOverrideCursor() self.hoverRubberBand.reset(QGis.Polygon) try: if self.toolAction: self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) except: pass def activate(self): """ Activate tool. """ if self.toolAction: self.toolAction.setChecked(True) QgsMapTool.activate(self) def setSelectionFeature(self, layer, feature, selectAll=False, setActiveLayer=False): """ Selects a given feature on canvas. :param layer: (QgsVectorLayer) layer containing the target feature. :param feature: (QgsFeature) taget feature to be selected. :param selectAll: (bool) indicates whether or not this fuction was called from a select all command. so it doesn't remove selection from those that are selected already from the list. :param setActiveLayer: (bool) indicates whether method should set layer as active. """ idList = layer.selectedFeaturesIds() featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) if setActiveLayer: layer.startEditing() self.iface.setActiveLayer(layer) return def setSelectionListFeature(self, dictLayerFeature, selectAll=True): """ Selects all features on canvas of a given dict. :param dictLayerFeature: (dict) dict of layers/features to be selected. :param selectAll: (bool) indicates if "All"-command comes from a "Select All". In that case, selected features won't be deselected. """ for layer in dictLayerFeature.keys(): geomType = layer.geometryType() # ID list of features already selected idList = layer.selectedFeaturesIds() # restart feature ID list for each layer featIdList = [] for feature in dictLayerFeature[layer]: featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) layer.startEditing() # last layer is set active and self.iface.setActiveLayer(layer) def openMultipleFeatureForm(self, dictLayerFeature): """ Opens all features Attribute Tables of a given list. :param dictLayerFeature: (dict) dict of layers/features to have their feature form exposed. """ for layer, features in dictLayerFeature.iteritems(): for feat in features: self.iface.openFeatureForm(layer, feat, showModal=False) def filterStrongestGeometry(self, dictLayerFeature): """ Filter a given dict of features for its strongest geometry. :param dictLayerFeature: (dict) a dict of layers and its features to be filtered. :return: (dict) filtered dict with only layers of the strongest geometry on original dict. """ strongest_geometry = 3 outDict = dict() if dictLayerFeature: for lyr in dictLayerFeature.keys(): # to retrieve strongest geometry value if strongest_geometry > lyr.geometryType(): strongest_geometry = lyr.geometryType() if strongest_geometry == 0: break for lyr in dictLayerFeature.keys(): if lyr.geometryType() == strongest_geometry: outDict[lyr] = dictLayerFeature[lyr] return outDict def createRubberBand(self, feature, layer, geom): """ Creates a rubber band around from a given a standard feature string. :param feature: taget feature to be highlighted :param layer: layer containing the target feature :param geom: int indicating geometry type of target feature """ if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) self.hoverRubberBand.addGeometry(feature.geometry(), layer) # to inform the code that menu has been hovered over self.menuHovered = True def createMultipleRubberBand(self, dictLayerFeature): """ Creates rubberbands around features. :param dictLayerFeature: (dict) dict of layer/features to have rubberbands built around. """ # only one type of geometry at a time will have rubberbands around it geom = dictLayerFeature.keys()[0].geometryType() if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) for layer, features in dictLayerFeature.iteritems(): for feat in features: self.hoverRubberBand.addGeometry(feat.geometry(), layer) self.menuHovered = True def checkSelectedLayers(self): """ Checks if there are layers selected on canvas. If there are, returns the geometry type of selected feature(s). If more than one type of feature is selected, the "strongest" geometry is returned. """ geom = None for layer in self.iface.legendInterface().layers(): if isinstance(layer, QgsVectorLayer): selection = layer.selectedFeatures() if len(selection): if geom == None: geom = layer.geometryType() continue elif layer.geometryType() < geom: geom = layer.geometryType() continue return geom def addCallBackToAction(self, action, onTriggeredAction, onHoveredAction=None): """ Adds action the command to the action. If onHoveredAction is given, signal "hovered" is applied with given action. :param action: (QAction) associated with target context menu. :param onTriggeredAction: (object) action to be executed when the given action is triggered. :param onHoveredAction: (object) action to be executed whilst the given action is hovered. """ action.triggered[()].connect(onTriggeredAction) if onHoveredAction: action.hovered[()].connect(onHoveredAction) def getCallback(self, e, layer, feature, geomType=None, selectAll=True): """ Gets the callback for an action. :param e: (QMouseEvent) mouse event on canvas. :param layer: (QgsVectorLayer) layer to be treated. :param feature: (QgsFeature) feature to be treated. :param geomType: (int) code indicating layer geometry type. It is retrieved OTF in case it's not given. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ if not geomType: geomType = layer.geometryType() if e.button() == QtCore.Qt.LeftButton: # line added to make sure the action is associated with current loop value, # lambda function is used with standard parameter set to current loops value. triggeredAction = lambda t=[ layer, feature ]: self.setSelectionFeature( t[0], feature=t[1], selectAll=selectAll, setActiveLayer=True) hoveredAction = lambda t=[layer, feature]: self.createRubberBand( feature=t[1], layer=t[0], geom=geomType) elif e.button() == QtCore.Qt.RightButton: selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: triggeredAction = lambda layer=layer: self.iface.setActiveLayer( layer) hoveredAction = None else: triggeredAction = lambda t=[ layer, feature ]: self.iface.openFeatureForm(t[0], t[1], showModal=False) hoveredAction = lambda t=[ layer, feature ]: self.createRubberBand( feature=t[1], layer=t[0], geom=geomType) return triggeredAction, hoveredAction def getCallbackMultipleFeatures(self, e, dictLayerFeature, selectAll=True): """ Sets the callback of an action with a list features as target. :param e: (QMouseEvent) mouse event on canvas. :param dictLayerFeature: (dict) dictionary containing layers/features to be treated. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ # setting the action for the "All" options if e.button() == QtCore.Qt.LeftButton: triggeredAction = lambda t=dictLayerFeature: self.setSelectionListFeature( dictLayerFeature=t, selectAll=selectAll) else: triggeredAction = lambda t=dictLayerFeature: self.openMultipleFeatureForm( dictLayerFeature=t) # to trigger "Hover" signal on QMenu for the multiple options hoveredAction = lambda t=dictLayerFeature: self.createMultipleRubberBand( dictLayerFeature=t) return triggeredAction, hoveredAction def createSubmenu(self, e, parentMenu, menuDict, genericAction, selectAll): """ Creates a submenu in a given parent context menu and populates it, with classes/feature sublevels from the menuDict. :param e: (QMouseEvent) mouse event on canvas. If menuDict has only 1 class in it, method will populate parent QMenu. :param parentMenu: (QMenu) menu containing the populated submenu :param menuDict: (dict) dictionary containing all classes and their features to be filled into submenu. :param genericAction: (str) text to be shown into generic action description on the outter level of submenu. :return: (dict) mapping of classes and their own QMenu object. """ # creating a dict to handle all "menu" for each class submenuDict = dict() for cl in menuDict.keys(): # menu for features of each class className = cl.name() geomType = cl.geometryType() # get layer database name dsUri = cl.dataProvider().dataSourceUri() temp = [] if '/' in dsUri or '\\' in dsUri: db_name = dsUri else: db_name = cl.dataProvider().dataSourceUri().split("'")[1] if len(menuDict) == 1: # if dictionaty has only 1 class, no need for an extra QMenu - features will be enlisted directly for feat in menuDict[cl]: s = '{0}.{1} (feat_id = {2})'.format( db_name, className, feat.id()) # inserting action for each feature action = parentMenu.addAction(s) triggeredAction, hoveredAction = self.getCallback( e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # inserting generic action, if necessary if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = parentMenu.addAction( self.tr("{0} From Class {1}").format( genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # there is no mapping of class to be exposed, only information added to parent QMenu itself return dict() submenuDict[cl] = QtGui.QMenu(title='{0}.{1}'.format( db_name, className), parent=parentMenu) parentMenu.addMenu(submenuDict[cl]) # inserting an entry for every feature of each class in its own context menu for feat in menuDict[cl]: s = 'feat_id = {0}'.format(feat.id()) action = submenuDict[cl].addAction(s) triggeredAction, hoveredAction = self.getCallback( e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # set up list for the "All"-commands temp.append([cl, feat, geomType]) # adding generic action for each class if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = submenuDict[cl].addAction( self.tr("{0} From Class {1}").format( genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature={cl: menuDict[cl]}, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) return submenuDict def setContextMenuStyle(self, e, dictMenuSelected, dictMenuNotSelected): """ Defines how many "submenus" the context menu should have. There are 3 context menu scenarios to be handled: :param e: (QMouseEvent) mouse event on canvas. :param dictMenuSelected: (dict) dictionary of classes and its selected features being treatead. :param dictMenuNotSelected: (dict) dictionary of classes and its non selected features being treatead. """ # finding out filling conditions selectedDict = bool(dictMenuSelected) notSelectedDict = bool(dictMenuNotSelected) # finding out if one of either dictionaty are filled ("Exclusive or") selectedXORnotSelected = (selectedDict != notSelectedDict) # setting "All"-command name if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Attribute Tables') else: genericAction = self.tr('Select All Features') # in case one of given dict is empty if selectedXORnotSelected: if selectedDict: menuDict, menu = dictMenuSelected, QtGui.QMenu( title=self.tr('Selected Features')) genericAction = self.tr('Deselect All Features') # if the dictionary is from selected features, we want commands to be able to deselect them selectAll = False else: menuDict, menu = dictMenuNotSelected, QtGui.QMenu( title=self.tr('Not Selected Features')) genericAction = self.tr('Select All Features') # if the dictionary is from non-selected features, we want commands to be able to select them selectAll = True if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Attribute Tables') self.createSubmenu(e=e, parentMenu=menu, menuDict=menuDict, genericAction=genericAction, selectAll=selectAll) if len(menuDict) != 1 and len(menuDict.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = menu.addAction(genericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) elif selectedDict: # if both of them is empty one more QMenu level is added menu = QtGui.QMenu() selectedMenu = QtGui.QMenu(title=self.tr('Selected Features')) notSelectedMenu = QtGui.QMenu( title=self.tr('Not Selected Features')) menu.addMenu(selectedMenu) menu.addMenu(notSelectedMenu) selectedGenericAction = self.tr('Deselect All Features') notSelectedGenericAction = self.tr('Select All Features') # selectAll is set to True as now we want command to Deselect Features in case they are selected self.createSubmenu(e=e, parentMenu=selectedMenu, menuDict=dictMenuSelected, genericAction=selectedGenericAction, selectAll=False) if len(dictMenuSelected) != 1 and len( dictMenuSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = selectedMenu.addAction(selectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=dictMenuSelected, selectAll=False) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) self.createSubmenu(e=e, parentMenu=notSelectedMenu, menuDict=dictMenuNotSelected, genericAction=notSelectedGenericAction, selectAll=True) if len(dictMenuNotSelected) != 1 and len( dictMenuNotSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = notSelectedMenu.addAction(notSelectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=dictMenuNotSelected, selectAll=True) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) menu.exec_(self.canvas.viewport().mapToGlobal(e.pos())) def checkSelectedFeaturesOnDict(self, menuDict): """ Checks all selected features from a given dictionary ( { (QgsVectorLayer)layer : [ (QgsFeature)feat ] } ). :param menuDict: (dict) dictionary with layers and their features to be analyzed. :return: (tuple-of-dict) both dictionaries of selected and non-selected features of each layer. """ selectedFeaturesDict, notSelectedFeaturesDict = dict(), dict() for cl in menuDict.keys(): selectedFeats = [f.id() for f in cl.selectedFeatures()] for feat in menuDict[cl]: if feat.id() in selectedFeats: if cl not in selectedFeaturesDict.keys(): selectedFeaturesDict[cl] = [feat] else: selectedFeaturesDict[cl].append(feat) else: if cl not in notSelectedFeaturesDict.keys(): notSelectedFeaturesDict[cl] = [feat] else: notSelectedFeaturesDict[cl].append(feat) return selectedFeaturesDict, notSelectedFeaturesDict def reprojectSearchArea(self, layer, geom): """ Reprojects search area if necessary, according to what is being searched. :param layer: (QgsVectorLayer) layer which target rectangle has to have same SRC. :param geom: (QgsRectangle) rectangle representing search area. """ #geom always have canvas coordinates epsg = self.canvas.mapSettings().destinationCrs().authid() #getting srid from something like 'EPSG:31983' srid = layer.crs().authid() if epsg == srid: return geom crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(srid) # Creating a transformer coordinateTransformer = QgsCoordinateTransform( crsSrc, crsDest) # here we have to put authid, not srid auxGeom = QgsGeometry.fromRect(geom) auxGeom.transform(coordinateTransformer) return auxGeom.boundingBox() def createContextMenu(self, e): """ Creates the context menu for overlapping layers. :param e: mouse event caught from canvas. """ selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: firstGeom = self.checkSelectedLayers() # setting a list of features to iterate over layerList = self.getPrimitiveDict(e, hasControlModifier=selected) layers = [] for key in layerList.keys(): layers += layerList[key] if layers: rect = self.getCursorRect(e) lyrFeatDict = dict() for layer in layers: if not isinstance(layer, QgsVectorLayer): continue geomType = layer.geometryType() # iterate over features inside the mouse bounding box bbRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, rect) for feature in layer.getFeatures(QgsFeatureRequest(bbRect)): geom = feature.geometry() if geom: searchRect = self.reprojectSearchArea(layer, rect) if selected: # if Control was held, appending behaviour is different if not firstGeom: firstGeom = geomType elif firstGeom > geomType: firstGeom = geomType if geomType == firstGeom and geom.intersects( searchRect): # only appends features if it has the same geometry as first selected feature if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] else: if geom.intersects(searchRect): if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] lyrFeatDict = self.filterStrongestGeometry(lyrFeatDict) if lyrFeatDict: moreThanOneFeat = lyrFeatDict.values() and len( lyrFeatDict.values()) > 1 or len( lyrFeatDict.values()[0]) > 1 if moreThanOneFeat: # if there are overlapping features (valid candidates only) selectedFeaturesDict, notSelectedFeaturesDict = self.checkSelectedFeaturesOnDict( menuDict=lyrFeatDict) self.setContextMenuStyle( e=e, dictMenuSelected=selectedFeaturesDict, dictMenuNotSelected=notSelectedFeaturesDict) else: layer = lyrFeatDict.keys()[0] feature = lyrFeatDict[layer][0] selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if e.button() == QtCore.Qt.LeftButton: # if feature is selected, we want it to be de-selected self.setSelectionFeature(layer=layer, feature=feature, selectAll=False, setActiveLayer=True) elif selected: self.iface.setActiveLayer(layer) else: self.iface.openFeatureForm(layer, feature, showModal=False)
class DuplicateTool(QgsMapTool): """ Map tool class to duplicate an object """ def __init__(self, iface): """ Constructor :param iface: interface """ QgsMapTool.__init__(self, iface.mapCanvas()) self.__iface = iface self.icon_path = ':/plugins/VDLTools/icons/duplicate_icon.png' self.text = QCoreApplication.translate("VDLTools", "Duplicate a feature") self.setCursor(Qt.ArrowCursor) self.__isEditing = False self.__layer = None self.__lastFeatureId = None self.__selectedFeature = None self.__rubberBand = None self.__newFeature = None self.__dstDlg = None def deactivate(self): """ When the action is deselected """ self.__cancel() QgsMapTool.deactivate(self) def toolName(self): """ To get the tool name :return: tool name """ return QCoreApplication.translate("VDLTools", "Duplicate") def startEditing(self): """ To set the action as enable, as the layer is editable """ self.action().setEnabled(True) Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer.editingStopped.connect(self.stopEditing) def stopEditing(self): """ To set the action as disable, as the layer is not editable """ self.action().setEnabled(False) Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) self.__layer.editingStarted.connect(self.startEditing) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() def setTool(self): """ To set the current tool as this one """ self.canvas().setMapTool(self) def __cancel(self): """ To cancel used variables """ self.__isEditing = False if self.__rubberBand is not None: self.canvas().scene().removeItem(self.__rubberBand) self.__rubberBand.reset() self.__rubberBand = None self.__dstDlg = None self.__newFeature = None self.__lastFeatureId = None self.__selectedFeature = None self.__layer.removeSelection() def __removeLayer(self): """ To remove the current working layer """ if self.__layer is not None: if self.__layer.isEditable(): Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) else: Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer = None def setEnable(self, layer): """ To check if we can enable the action for the selected layer :param layer: selected layer """ types = [QGis.Line, QGis.Polygon] if layer is not None and layer.type( ) == QgsMapLayer.VectorLayer and layer.geometryType() in types: if layer == self.__layer: return if self.__layer is not None: if self.__layer.isEditable(): Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing) else: Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing) self.__layer = layer if self.__layer.isEditable(): self.action().setEnabled(True) self.__layer.editingStopped.connect(self.stopEditing) else: self.action().setEnabled(False) self.__layer.editingStarted.connect(self.startEditing) if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() return if self.canvas().mapTool() == self: self.__iface.actionPan().trigger() self.action().setEnabled(False) self.__removeLayer() def __setDistanceDialog(self, isComplexPolygon): """ To create a Duplicate Distance Dialog :param isComplexPolygon: for a polygon, if it has interior ring(s) """ self.__dstDlg = DuplicateDistanceDialog(isComplexPolygon) self.__dstDlg.rejected.connect(self.__cancel) self.__dstDlg.previewButton().clicked.connect(self.__onDstPreview) self.__dstDlg.okButton().clicked.connect(self.__onDstOk) self.__dstDlg.cancelButton().clicked.connect(self.__onDstCancel) self.__dstDlg.directionCheck().stateChanged.connect( self.__onDstPreview) def __onDstCancel(self): """ When the Cancel button in Duplicate Distance Dialog is pushed """ self.__dstDlg.reject() @staticmethod def __newPoint(angle, point, distance): """ To create a new point at a certain distance and certain azimut from another point :param angle: the azimut :param point: the reference point :param distance: the distance :return: the new QgsPoint (with same elevation than parameter point) """ x = point.x() + cos(angle) * distance y = point.y() + sin(angle) * distance pt = QgsPointV2(x, y) pt.addZValue(point.z()) return pt def __onDstPreview(self): """ When the Preview button in Duplicate Distance Dialog is pushed """ if self.__rubberBand is not None: self.canvas().scene().removeItem(self.__rubberBand) self.__rubberBand = None if self.__dstDlg.distanceEdit().text() is not None: distance = float(self.__dstDlg.distanceEdit().text()) if self.__dstDlg.directionCheck().checkState(): distance = -distance if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(distance) else: self.__linePreview(distance) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setWidth(2) self.__rubberBand.setColor(color) self.__rubberBand.setLineStyle(Qt.DotLine) self.__rubberBand.show() def __linePreview(self, distance): """ To create the preview (rubberBand) of the duplicate line at a certain distance :param distance: the given distance """ self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line) line_v2, curved = GeometryV2.asLineV2( self.__selectedFeature.geometry(), self.__iface) if isinstance(curved, (list, tuple)): self.__newFeature = QgsCompoundCurveV2() for pos in range(line_v2.nCurves()): if curved[pos]: curve_v2 = self.__newArc(line_v2.curveAt(pos), distance) else: curve_v2 = self.__newLine(line_v2.curveAt(pos), distance) self.__newFeature.addCurve(curve_v2) if pos == 0: self.__rubberBand.setToGeometry( QgsGeometry(curve_v2.curveToLine()), None) else: self.__rubberBand.addGeometry( QgsGeometry(curve_v2.curveToLine()), None) else: if curved: self.__newFeature = self.__newArc(line_v2, distance) else: self.__newFeature = self.__newLine(line_v2, distance) self.__rubberBand.setToGeometry( QgsGeometry(self.__newFeature.curveToLine()), None) def __newArc(self, arc_v2, distance): """ To duplicate a curve at a given distance :param arc_v2: the curve to duplicate :param distance: the given distance :return: the new curve """ curve_v2 = QgsCircularStringV2() points = [] circle = Circle(arc_v2.pointN(0), arc_v2.pointN(1), arc_v2.pointN(2)) points.append( self.__newPoint(circle.angle1(), arc_v2.pointN(0), distance)) points.append( self.__newPoint(circle.angle2(), arc_v2.pointN(1), distance)) points.append( self.__newPoint(circle.angle3(), arc_v2.pointN(2), distance)) curve_v2.setPoints(points) return curve_v2 def __newLine(self, line_v2, distance): """ To duplicate a line at a given distance :param line_v2: the line to duplicate :param distance: the given distance :return: the new line """ curve_v2 = QgsLineStringV2() points = [] for pos in range(line_v2.numPoints()): if pos == 0: angle = Circle.angle(line_v2.pointN(pos), line_v2.pointN(pos + 1)) + old_div(pi, 2) dist = distance elif pos == (line_v2.numPoints() - 1): angle = Circle.angle(line_v2.pointN(pos - 1), line_v2.pointN(pos)) + old_div(pi, 2) dist = distance else: angle1 = Circle.angle(line_v2.pointN(pos - 1), line_v2.pointN(pos)) angle2 = Circle.angle(line_v2.pointN(pos), line_v2.pointN(pos + 1)) angle = old_div(float(pi + angle1 + angle2), 2) dist = old_div(float(distance), sin(old_div(float(pi + angle1 - angle2), 2))) points.append(self.__newPoint(angle, line_v2.pointN(pos), dist)) curve_v2.setPoints(points) return curve_v2 def __polygonPreview(self, distance): """ To create the preview (rubberBand) of the duplicate polygon at a certain distance :param distance: the given distance """ self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line) polygon_v2, curved = GeometryV2.asPolygonV2( self.__selectedFeature.geometry(), self.__iface) self.__newFeature = QgsCurvePolygonV2() line_v2 = self.__newPolygonCurve(polygon_v2.exteriorRing(), distance, curved[0]) self.__newFeature.setExteriorRing(line_v2) self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()), None) for num in range(polygon_v2.numInteriorRings()): if self.__dstDlg.isInverted(): distance = -distance line_v2 = self.__newPolygonCurve(polygon_v2.interiorRing(num), distance, curved[num + 1]) self.__newFeature.addInteriorRing(line_v2) self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()), None) def __newPolygonCurve(self, curve_v2, distance, curved): """ To create a duplicate curve for a polygon curves :param curve_v2: curve to duplicate :param distance: distance where to :param curved: if the line is curved :return: new duplicate curve """ if curved: new_line_v2 = QgsCircularStringV2() else: new_line_v2 = QgsLineStringV2() points = [] for pos in range(curve_v2.numPoints()): if pos == 0: pos1 = curve_v2.numPoints() - 2 else: pos1 = pos - 1 pos2 = pos if pos == (curve_v2.numPoints() - 1): pos3 = 1 else: pos3 = pos + 1 angle1 = Circle.angle(curve_v2.pointN(pos1), curve_v2.pointN(pos2)) angle2 = Circle.angle(curve_v2.pointN(pos), curve_v2.pointN(pos3)) angle = old_div(float(pi + angle1 + angle2), 2) dist = old_div(float(distance), sin(old_div(float(pi + angle1 - angle2), 2))) points.append(self.__newPoint(angle, curve_v2.pointN(pos), dist)) new_line_v2.setPoints(points) return new_line_v2 def __onDstOk(self): """ When the Ok button in Duplicate Distance Dialog is pushed """ self.__onDstPreview() self.__dstDlg.accept() geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage(QCoreApplication.translate( "VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0) feature = QgsFeature(self.__layer.pendingFields()) feature.setGeometry(geometry) primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn() for field in self.__selectedFeature.fields(): if field.name() != primaryKey: feature.setAttribute( field.name(), self.__selectedFeature.attribute(field.name())) if len(self.__selectedFeature.fields()) > 0 and self.__layer.editFormConfig().suppress() != \ QgsEditFormConfig.SuppressOn: self.__iface.openFeatureForm(self.__layer, feature) else: self.__layer.addFeature(feature) # self.__layer.updateExtents() self.__cancel() def canvasMoveEvent(self, event): """ When the mouse is moved :param event: mouse event """ if not self.__isEditing: laySettings = QgsSnappingUtils.LayerConfig(self.__layer, QgsPointLocator.All, 10, QgsTolerance.Pixels) feat = Finder.findClosestFeatureAt(event.mapPoint(), laySettings, self) if feat is not None and self.__lastFeatureId != feat.id(): self.__lastFeatureId = feat.id() self.__layer.setSelectedFeatures([feat.id()]) if feat is None: self.__layer.removeSelection() self.__lastFeatureId = None def canvasReleaseEvent(self, event): """ When the mouse is clicked :param event: mouse event """ found_features = self.__layer.selectedFeatures() if len(found_features) > 0: if len(found_features) > 1: self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "One feature at a time"), level=QgsMessageBar.INFO) return self.__selectedFeature = found_features[0] self.__isEditing = True if self.__layer.geometryType() == QGis.Polygon\ and len(self.__selectedFeature.geometry().asPolygon()) > 1: self.__setDistanceDialog(True) else: self.__setDistanceDialog(False) self.__dstDlg.distanceEdit().setText("5.0") self.__dstDlg.distanceEdit().selectAll() self.__dstDlg.show()
class ShLocatorFilter(QgsLocatorFilter): HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS ShLocator Filter'} message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget) def __init__(self, iface: QgisInterface = None): """" :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise """ super().__init__() self.iface = iface self.settings = Settings() # following properties will only be used in main thread self.rubber_band = None self.map_canvas: QgsMapCanvas = None self.transform_ch = None self.current_timer = None self.result_found = False self.nam_fetch_feature = None if iface is not None: # happens only in main thread self.map_canvas = iface.mapCanvas() self.map_canvas.destinationCrsChanged.connect( self.create_transforms) self.rubber_band = QgsRubberBand( self.map_canvas, QgsWkbTypes.PolygonGeometry) self.rubber_band.setColor(QColor(255, 50, 50, 200)) self.rubber_band.setFillColor(QColor(255, 255, 50, 160)) self.rubber_band.setBrushStyle(Qt.SolidPattern) self.rubber_band.setLineStyle(Qt.SolidLine) self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE) self.rubber_band.setIconSize(15) self.rubber_band.setWidth(4) self.rubber_band.setBrushStyle(Qt.NoBrush) self.create_transforms() def name(self): return 'ShLocator' def clone(self): return ShLocatorFilter() def priority(self): return QgsLocatorFilter.Highest def displayName(self): return 'ShLocator' def prefix(self): return 'shl' def clearPreviousResults(self): if self.rubber_band: self.rubber_band.reset(QgsWkbTypes.PointGeometry) if self.current_timer is not None: self.current_timer.stop() self.current_timer.deleteLater() self.current_timer = None def hasConfigWidget(self): return True def openConfigWidget(self, parent=None): dlg = ConfigDialog(parent) dlg.exec_() def create_transforms(self): # this should happen in the main thread src_crs_ch = QgsCoordinateReferenceSystem('EPSG:2056') assert src_crs_ch.isValid() dst_crs = self.map_canvas.mapSettings().destinationCrs() self.transform_ch = QgsCoordinateTransform( src_crs_ch, dst_crs, QgsProject.instance()) @staticmethod def url_with_param(url, params) -> str: url = QUrl(url) q = QUrlQuery(url) for key, value in params.items(): q.addQueryItem(key, value) url.setQuery(q) return url.url() def fetchResults(self, search: str, context: QgsLocatorContext, feedback: QgsFeedback): try: dbg_info("start shlocator search...") if len(search) < 3: return self.result_found = False params = { 'query': str(search), 'maxfeatures': str(self.settings.value('max_features')) } nam = NetworkAccessManager() feedback.canceled.connect(nam.abort) url = self.url_with_param( self.settings.value('service_url'), params) dbg_info(url) try: (response, content) = nam.request( url, headers=self.HEADERS, blocking=True) self.handle_response(response, search) except RequestsExceptionUserAbort: pass except RequestsException as err: info(err, Qgis.Info) if not self.result_found: result = QgsLocatorResult() result.filter = self result.displayString = self.tr('No result found.') result.userData = NoResult self.resultFetched.emit(result) except Exception as e: info(e, Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) info(traceback.print_exception( exc_type, exc_obj, exc_traceback), Qgis.Critical) def data_product_qgsresult(self, data: dict) -> QgsLocatorResult: result = QgsLocatorResult() result.filter = self result.displayString = data['display'] result.group = 'Karten' result.userData = DataProductResult( type=data['type'], dataproduct_id=data['dataproduct_id'], display=data['display'], dset_info=data['dset_info'], sublayers=data.get('sublayers', None) ) data_product = 'dataproduct' data_type = data['type'] result.icon, result.description = dataproduct2icon_description( data_product, data_type) return result def handle_response(self, response, search_text: str): try: if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): info("Error in main response with status code: " "{} from {}".format(response.status_code, response.url)) return display_name_field = QgsField('display_name', QVariant.String) fields = QgsFields() fields.append(display_name_field) features = QgsJsonUtils.stringToFeatureList(response.content.decode('utf-8'), fields, QTextCodec.codecForName('UTF-8')) dbg_info('Found {} features'.format(len(features))) dbg_info('Data {}'.format(response.content.decode('utf-8'))) for feature in features: dbg_info('Adding feature {}'.format(feature['display_name'])) result = QgsLocatorResult() result.filter = self result.group = 'Objekte' result.displayString = feature['display_name'] result.userData = FeatureResult(feature) self.resultFetched.emit(result) self.result_found = True except Exception as e: info(str(e), Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) info(traceback.print_exception( exc_type, exc_obj, exc_traceback), Qgis.Critical) def triggerResult(self, result: QgsLocatorResult): # this is run in the main thread, i.e. map_canvas is not None self.clearPreviousResults() if type(result.userData) == NoResult: pass elif type(result.userData) == FeatureResult: self.highlight(result.userData.feature.geometry()) else: info('Incorrect result. Please contact support', Qgis.Critical) def highlight(self, geometry: QgsGeometry): self.rubber_band.reset(geometry.type()) self.rubber_band.addGeometry(geometry, None) rect = geometry.boundingBox() if not self.settings.value('keep_scale'): if rect.isEmpty(): current_extent = self.map_canvas.extent() rect = current_extent.scaled(self.settings.value( 'point_scale')/self.map_canvas.scale(), rect.center()) else: rect.scale(4) self.map_canvas.setExtent(rect) self.map_canvas.refresh() # self.current_timer = QTimer() # self.current_timer.timeout.connect(self.clearPreviousResults) # self.current_timer.setSingleShot(True) # self.current_timer.start(5000) def parse_feature_response(self, response): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): info("Error in feature response with status code: " "{} from {}".format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) geometry_type = data['geometry']['type'] geometry = QgsGeometry() if geometry_type == 'Point': geometry = QgsGeometry.fromPointXY(QgsPointXY(data['geometry']['coordinates'][0], data['geometry']['coordinates'][1])) elif geometry_type == 'Polygon': rings = data['geometry']['coordinates'] for r in range(0, len(rings)): for p in range(0, len(rings[r])): rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1]) geometry = QgsGeometry.fromPolygonXY(rings) elif geometry_type == 'MultiPolygon': islands = data['geometry']['coordinates'] for i in range(0, len(islands)): for r in range(0, len(islands[i])): for p in range(0, len(islands[i][r])): islands[i][r][p] = QgsPointXY( islands[i][r][p][0], islands[i][r][p][1]) geometry = QgsGeometry.fromMultiPolygonXY(islands) else: # ShLocator does not handle {geometry_type} yet. Please contact support info('ShLocator unterstützt den Geometrietyp {geometry_type} nicht.' ' Bitte kontaktieren Sie den Support.'.format(geometry_type=geometry_type), Qgis.Warning) geometry.transform(self.transform_ch) self.highlight(geometry)
class LatLonTools: digitizerDialog = None toMGRSDialog = None MGRStoLayerDialog = None geom2FieldDialog = None def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.crossRb.setColor(Qt.red) self.provider = LatLonToolsProvider() def initGui(self): '''Initialize Lot Lon Tools GUI.''' # Initialize the Settings Dialog box self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow()) self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface) self.showMapTool = ShowOnMapTool(self.settingsDialog, self.iface) # Add Interface for Coordinate Capturing icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png") self.copyAction = QAction(icon, "Copy Latitude, Longitude", self.iface.mainWindow()) self.copyAction.setObjectName('latLonToolsCopy') self.copyAction.triggered.connect(self.startCapture) self.copyAction.setCheckable(True) self.iface.addToolBarIcon(self.copyAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction) # Add Interface for Zoom to Coordinate icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png") self.zoomToAction = QAction(icon, "Zoom To Latitude, Longitude", self.iface.mainWindow()) self.zoomToAction.setObjectName('latLonToolsZoom') self.zoomToAction.triggered.connect(self.showZoomToDialog) self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction) self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog) self.zoomToDialog.hide() # Add Interface for External Map icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png") self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow()) self.externMapAction.setObjectName('latLonToolsExternalMap') self.externMapAction.triggered.connect(self.setShowMapTool) self.externMapAction.setCheckable(True) self.iface.addToolBarIcon(self.externMapAction) self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction) # Add Interface for Multi point zoom icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png') self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow()) self.multiZoomToAction.setObjectName('latLonToolsMultiZoom') self.multiZoomToAction.triggered.connect(self.multiZoomTo) self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction) self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow()) self.multiZoomDialog.hide() self.multiZoomDialog.setFloating(True) # Add To MGRS conversion menu = QMenu() icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.png') action = menu.addAction(icon, "Geometry to Field", self.geom2Field) action.setObjectName('latLonToolsGeom2Field') icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png') action = menu.addAction(icon, "MGRS to Geometry", self.MGRStoLayer) action.setObjectName('latLonToolsMGRS2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png') action = menu.addAction(icon, "Geometry to MGRS", self.toMGRS) action.setObjectName('latLonToolsGeom2MGRS') self.toMGRSAction = QAction(icon, "Conversions", self.iface.mainWindow()) self.toMGRSAction.setMenu(menu) self.iface.addPluginToMenu('Lat Lon Tools', self.toMGRSAction) # Initialize the Settings Dialog Box settingsicon = QIcon(os.path.dirname(__file__) + '/images/settings.png') self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow()) self.settingsAction.setObjectName('latLonToolsSettings') self.settingsAction.triggered.connect(self.settings) self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction) # Help icon = QIcon(os.path.dirname(__file__) + '/images/help.png') self.helpAction = QAction(icon, "Help", self.iface.mainWindow()) self.helpAction.setObjectName('latLonToolsHelp') self.helpAction.triggered.connect(self.help) self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction) # Add to Digitize Toolbar icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.png') self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow()) self.digitizeAction.setObjectName('latLonToolsDigitize') self.digitizeAction.triggered.connect(self.digitizeClicked) self.digitizeAction.setEnabled(False) self.iface.digitizeToolBar().addAction(self.digitizeAction) self.iface.currentLayerChanged.connect(self.currentLayerChanged) self.canvas.mapToolSet.connect(self.unsetTool) self.enableDigitizeTool() # Add the processing provider QgsApplication.processingRegistry().addProvider(self.provider) def unsetTool(self, tool): '''Uncheck the Copy Lat Lon tool''' try: if not isinstance(tool, CopyLatLonTool): self.copyAction.setChecked(False) self.multiZoomDialog.stopCapture() self.mapTool.capture4326 = False if not isinstance(tool, ShowOnMapTool): self.externMapAction.setChecked(False) except: pass def unload(self): '''Unload LatLonTools from the QGIS interface''' self.zoomToDialog.removeMarker() self.multiZoomDialog.removeMarkers() self.canvas.unsetMapTool(self.mapTool) self.canvas.unsetMapTool(self.showMapTool) self.iface.removePluginMenu('Lat Lon Tools', self.copyAction) self.iface.removeToolBarIcon(self.copyAction) self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction) self.iface.removeToolBarIcon(self.externMapAction) self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.toMGRSAction) self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction) self.iface.removePluginMenu('Lat Lon Tools', self.helpAction) self.iface.removeDockWidget(self.zoomToDialog) self.iface.removeDockWidget(self.multiZoomDialog) self.geom2FieldDialog = None self.MGRStoLayerDialog = None self.toMGRSDialog = None self.zoomToDialog = None self.multiZoomDialog = None self.settingsDialog = None self.showMapTool = None self.mapTool = None self.iface.digitizeToolBar().removeAction(self.digitizeAction) self.digitizerDialog = None QgsApplication.processingRegistry().removeProvider(self.provider) def startCapture(self): '''Set the focus of the copy coordinate tool and check it''' self.copyAction.setChecked(True) self.canvas.setMapTool(self.mapTool) def setShowMapTool(self): '''Set the focus of the external map tool and check it''' self.externMapAction.setChecked(True) self.canvas.setMapTool(self.showMapTool) def showZoomToDialog(self): '''Show the zoom to docked widget.''' self.zoomToDialog.show() def multiZoomTo(self): '''Display the Multi-zoom to dialog box''' self.multiZoomDialog.show() def geom2Field(self): '''Convert layer geometry to a text string''' if self.geom2FieldDialog == None: from .geom2field import Geom2FieldWidget self.geom2FieldDialog = Geom2FieldWidget(self.iface, self.iface.mainWindow()) self.geom2FieldDialog.show() def toMGRS(self): '''Display the to MGRS dialog box''' results = processing.execAlgorithmDialog('latlontools:point2mgrs', {}) def MGRStoLayer(self): '''Display the to MGRS dialog box''' results = processing.execAlgorithmDialog('latlontools:mgrs2point', {}) def settings(self): '''Show the settings dialog box''' self.settingsDialog.show() def help(self): '''Display a help page''' url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2) def settingsChanged(self): # Settings may have changed so we need to make sure the zoomToDialog window is configured properly self.zoomToDialog.configure() self.multiZoomDialog.settingsChanged() def zoomTo(self, srcCrs, lat, lon): canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(srcCrs, canvasCrs, QgsProject.instance()) x, y = transform.transform(float(lon), float(lat)) rect = QgsRectangle(x,y,x,y) self.canvas.setExtent(rect) pt = QgsPointXY(x,y) self.highlight(pt) self.canvas.refresh() return pt def highlight(self, point): currExt = self.canvas.extent() leftPt = QgsPoint(currExt.xMinimum(),point.y()) rightPt = QgsPoint(currExt.xMaximum(),point.y()) topPt = QgsPoint(point.x(),currExt.yMaximum()) bottomPt = QgsPoint(point.x(),currExt.yMinimum()) horizLine = QgsGeometry.fromPolyline( [ leftPt , rightPt ] ) vertLine = QgsGeometry.fromPolyline( [ topPt , bottomPt ] ) self.crossRb.reset(QgsWkbTypes.LineGeometry) self.crossRb.addGeometry(horizLine, None) self.crossRb.addGeometry(vertLine, None) QTimer.singleShot(700, self.resetRubberbands) def resetRubberbands(self): self.crossRb.reset() def digitizeClicked(self): if self.digitizerDialog == None: from .digitizer import DigitizerWidget self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow()) self.digitizerDialog.show() def currentLayerChanged(self): layer = self.iface.activeLayer() if layer != None: try: layer.editingStarted.disconnect(self.layerEditingChanged) except: pass try: layer.editingStopped.disconnect(self.layerEditingChanged) except: pass if isinstance(layer, QgsVectorLayer): layer.editingStarted.connect(self.layerEditingChanged) layer.editingStopped.connect(self.layerEditingChanged) self.enableDigitizeTool() def layerEditingChanged(self): self.enableDigitizeTool() def enableDigitizeTool(self): self.digitizeAction.setEnabled(False) layer = self.iface.activeLayer() if layer != None and isinstance(layer, QgsVectorLayer) and (layer.wkbType() == QgsWkbTypes.Point) and layer.isEditable(): self.digitizeAction.setEnabled(True) else: if self.digitizerDialog != None: self.digitizerDialog.close()
class PlanetCircleMapTool(QgsMapTool): circleSelected = pyqtSignal(object) def __init__(self, canvas): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.circle = QgsGeometry self.dragging = False self.rubber_band = None self.center = QPointF() self.tangent_point = QPointF() self.radius = 0.0 def canvasPressEvent(self, event): self.center = event.pos() self.rubber_band = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubber_band.setFillColor(RB_FILL) self.rubber_band.setStrokeColor(RB_STROKE) self.rubber_band.setWidth(1) def canvasMoveEvent(self, event): if event.buttons() != Qt.LeftButton: return if not self.dragging: self.dragging = True self.center = event.pos() self.tangent_point = event.pos() self.radius = sqrt(QPointF.dotProduct(self.center, self.tangent_point)) self._set_rubber_band() def canvasReleaseEvent(self, event): # If the user simply clicked without dragging ignore this if not self.dragging: return if self.rubber_band: self._set_rubber_band() self.rubber_band.reset(QgsWkbTypes.PolygonGeometry) del self.rubber_band self.rubber_band = None self.dragging = False # noinspection PyUnresolvedReferences self.circleSelected.emit(self.circle) def _set_rubber_band(self): transform = self.canvas.getCoordinateTransform() rb_center = transform.toMapCoordinates(self.center) rb_tangent = transform.toMapCoordinates(self.tangent_point) rb_circle = QgsCircle( QgsPoint(rb_center.x(), rb_center.y()), rb_center.distance(rb_tangent.x(), rb_tangent.y())) circle_geom = QgsGeometry(rb_circle.toPolygon()) if self.rubber_band: self.rubber_band.reset(QgsWkbTypes.PolygonGeometry) self.rubber_band.addGeometry(circle_geom) self.circle = circle_geom
class Qgis2threejsDialog(QDialog): def __init__(self, iface, objectTypeManager, pluginManager, exportSettings=None, lastTreeItemData=None): QDialog.__init__(self, iface.mainWindow()) self.iface = iface self.objectTypeManager = objectTypeManager self.pluginManager = pluginManager self._settings = exportSettings or {} self.lastTreeItemData = lastTreeItemData self.localBrowsingMode = True self.rb_quads = self.rb_point = None self.templateType = None self.currentItem = None self.currentPage = None # Set up the user interface from Designer. self.ui = ui = Ui_Qgis2threejsDialog() ui.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint) # output html filename ui.lineEdit_OutputFilename.setText(self._settings.get("OutputFilename", "")) ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]") # settings button icon = QIcon(os.path.join(tools.pluginDir(), "icons", "settings.png")) ui.toolButton_Settings.setIcon(icon) # popup menu displayed when settings button is pressed items = [["Load Settings...", self.loadSettings], ["Save Settings As...", self.saveSettings], [None, None], ["Clear Settings", self.clearSettings], [None, None], ["Plugin Settings...", self.pluginSettings]] self.menu = QMenu() self.menu_actions = [] for text, slot in items: if text: action = QAction(text, iface.mainWindow()) action.triggered.connect(slot) self.menu.addAction(action) self.menu_actions.append(action) else: self.menu.addSeparator() ui.toolButton_Settings.setMenu(self.menu) ui.toolButton_Settings.setPopupMode(QToolButton.InstantPopup) # progress bar and message label ui.progressBar.setVisible(False) ui.label_MessageIcon.setVisible(False) # buttons ui.pushButton_Run.clicked.connect(self.run) ui.pushButton_Close.clicked.connect(self.reject) ui.pushButton_Help.clicked.connect(self.help) # set up map tool self.previousMapTool = None self.mapTool = RectangleMapTool(iface.mapCanvas()) #self.mapTool = PointMapTool(iface.mapCanvas()) # set up the template combo box self.initTemplateList() self.ui.comboBox_Template.currentIndexChanged.connect(self.currentTemplateChanged) # set up the properties pages self.pages = {} self.pages[ppages.PAGE_WORLD] = ppages.WorldPropertyPage(self) self.pages[ppages.PAGE_CONTROLS] = ppages.ControlsPropertyPage(self) self.pages[ppages.PAGE_DEM] = ppages.DEMPropertyPage(self) self.pages[ppages.PAGE_VECTOR] = ppages.VectorPropertyPage(self) container = ui.propertyPagesContainer for page in self.pages.itervalues(): page.hide() container.addWidget(page) # build object tree self.topItemPages = {ObjectTreeItem.ITEM_WORLD: ppages.PAGE_WORLD, ObjectTreeItem.ITEM_CONTROLS: ppages.PAGE_CONTROLS, ObjectTreeItem.ITEM_DEM: ppages.PAGE_DEM} self.initObjectTree() self.ui.treeWidget.currentItemChanged.connect(self.currentObjectChanged) self.ui.treeWidget.itemChanged.connect(self.objectItemChanged) self.currentTemplateChanged() # update item visibility ui.toolButton_Browse.clicked.connect(self.browseClicked) #iface.mapCanvas().mapToolSet.connect(self.mapToolSet) # to show button to enable own map tool def settings(self, clean=False): # save settings of current panel item = self.ui.treeWidget.currentItem() if item and self.currentPage: self.saveProperties(item, self.currentPage) # plugin version self._settings["PluginVersion"] = plugin_version # template and output html file path self._settings["Template"] = self.ui.comboBox_Template.currentText() self._settings["OutputFilename"] = self.ui.lineEdit_OutputFilename.text() if not clean: return self._settings # clean up settings - remove layers that don't exist in the layer registry registry = QgsMapLayerRegistry.instance() for itemId in [ObjectTreeItem.ITEM_OPTDEM, ObjectTreeItem.ITEM_POINT, ObjectTreeItem.ITEM_LINE, ObjectTreeItem.ITEM_POLYGON]: parent = self._settings.get(itemId, {}) for layerId in parent.keys(): if registry.mapLayer(layerId) is None: del parent[layerId] return self._settings def setSettings(self, settings): self._settings = settings # template and output html file path templateName = settings.get("Template") if templateName: cbox = self.ui.comboBox_Template index = cbox.findText(templateName) if index != -1: cbox.setCurrentIndex(index) filename = settings.get("OutputFilename") if filename: self.ui.lineEdit_OutputFilename.setText(filename) # update object tree self.ui.treeWidget.blockSignals(True) self.initObjectTree() self.ui.treeWidget.blockSignals(False) # update tree item visibility self.templateType = None self.currentTemplateChanged() def loadSettings(self): # file open dialog directory = QgsProject.instance().homePath() if not directory: directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0] if not directory: directory = QDir.homePath() filterString = "Settings files (*.qto3settings);;All files (*.*)" filename = QFileDialog.getOpenFileName(self, "Load Export Settings", directory, filterString) if not filename: return # load settings from file (.qto3settings) import json with open(filename) as f: settings = json.load(f) self.setSettings(settings) def saveSettings(self, filename=None): if not filename: # file save dialog directory = QgsProject.instance().homePath() if not directory: directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0] if not directory: directory = QDir.homePath() filename = QFileDialog.getSaveFileName(self, "Save Export Settings", directory, "Settings files (*.qto3settings)") if not filename: return # append .qto3settings extension if filename doesn't have if os.path.splitext(filename)[1].lower() != ".qto3settings": filename += ".qto3settings" # save settings to file (.qto3settings) import codecs import json with codecs.open(filename, "w", "UTF-8") as f: json.dump(self.settings(True), f, ensure_ascii=False, indent=2, sort_keys=True) logMessage(u"Settings saved: {0}".format(filename)) def clearSettings(self): if QMessageBox.question(self, "Qgis2threejs", "Are you sure to clear all export settings?", QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok: self.setSettings({}) def pluginSettings(self): from settingsdialog import SettingsDialog dialog = SettingsDialog(self) if dialog.exec_(): self.pluginManager.reloadPlugins() self.pages[ppages.PAGE_DEM].initLayerComboBox() def showMessageBar(self, text, level=QgsMessageBar.INFO): # from src/gui/qgsmessagebaritem.cpp if level == QgsMessageBar.CRITICAL: msgIcon = "/mIconCritical.png" bgColor = "#d65253" elif level == QgsMessageBar.WARNING: msgIcon = "/mIconWarn.png" bgColor = "#ffc800" else: msgIcon = "/mIconInfo.png" bgColor = "#e7f5fe" stylesheet = "QLabel {{ background-color:{0}; }}".format(bgColor) label = self.ui.label_MessageIcon label.setPixmap(QgsApplication.getThemeIcon(msgIcon).pixmap(24)) label.setStyleSheet(stylesheet) label.setVisible(True) label = self.ui.label_Status label.setText(text) label.setStyleSheet(stylesheet) def clearMessageBar(self): self.ui.label_MessageIcon.setVisible(False) self.ui.label_Status.setText("") self.ui.label_Status.setStyleSheet("QLabel { background-color: rgba(0, 0, 0, 0); }") def initTemplateList(self): cbox = self.ui.comboBox_Template cbox.clear() templateDir = QDir(tools.templateDir()) for i, entry in enumerate(templateDir.entryList(["*.html", "*.htm"])): cbox.addItem(entry) config = tools.getTemplateConfig(entry) # get template type templateType = config.get("type", "plain") cbox.setItemData(i, templateType, Qt.UserRole) # set tool tip text desc = config.get("description", "") if desc: cbox.setItemData(i, desc, Qt.ToolTipRole) # select the template of the settings templatePath = self._settings.get("Template") # if no template setting, select the last used template if not templatePath: templatePath = QSettings().value("/Qgis2threejs/lastTemplate", def_vals.template, type=unicode) if templatePath: index = cbox.findText(templatePath) if index != -1: cbox.setCurrentIndex(index) return index return -1 def initObjectTree(self): tree = self.ui.treeWidget tree.clear() # add vector and raster layers into tree widget topItems = {} for id, name in zip(ObjectTreeItem.topItemIds, ObjectTreeItem.topItemNames): item = QTreeWidgetItem(tree, [name]) item.setData(0, Qt.UserRole, id) topItems[id] = item optDEMChecked = False for layer in self.iface.legendInterface().layers(): parentId = ObjectTreeItem.parentIdByLayer(layer) if parentId is None: continue item = QTreeWidgetItem(topItems[parentId], [layer.name()]) isVisible = self._settings.get(parentId, {}).get(layer.id(), {}).get("visible", False) #self.iface.legendInterface().isLayerVisible(layer) check_state = Qt.Checked if isVisible else Qt.Unchecked item.setData(0, Qt.CheckStateRole, check_state) item.setData(0, Qt.UserRole, layer.id()) if parentId == ObjectTreeItem.ITEM_OPTDEM and isVisible: optDEMChecked = True for id, item in topItems.iteritems(): if id != ObjectTreeItem.ITEM_OPTDEM or optDEMChecked: tree.expandItem(item) # disable additional DEM item which is selected as main DEM layerId = self._settings.get(ObjectTreeItem.ITEM_DEM, {}).get("comboBox_DEMLayer") if layerId: self.primaryDEMChanged(layerId) def saveProperties(self, item, page): properties = page.properties() parent = item.parent() if parent is None: # top level item self._settings[item.data(0, Qt.UserRole)] = properties else: # layer item parentId = parent.data(0, Qt.UserRole) if parentId not in self._settings: self._settings[parentId] = {} self._settings[parentId][item.data(0, Qt.UserRole)] = properties def setCurrentTreeItemByData(self, data): it = QTreeWidgetItemIterator(self.ui.treeWidget) while it.value(): if it.value().data(0, Qt.UserRole) == data: self.ui.treeWidget.setCurrentItem(it.value()) return True it += 1 return False def currentTemplateChanged(self, index=None): cbox = self.ui.comboBox_Template templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole) if templateType == self.templateType: return # hide items unsupported by template tree = self.ui.treeWidget for i, id in enumerate(ObjectTreeItem.topItemIds): hidden = (templateType == "sphere" and id != ObjectTreeItem.ITEM_CONTROLS) tree.topLevelItem(i).setHidden(hidden) # set current tree item if templateType == "sphere": tree.setCurrentItem(tree.topLevelItem(ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_CONTROLS))) elif self.lastTreeItemData is None or not self.setCurrentTreeItemByData(self.lastTreeItemData): # restore selection tree.setCurrentItem(tree.topLevelItem(ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_DEM))) # default selection for plain is DEM # display messages self.clearMessageBar() if templateType != "sphere": # show message if crs unit is degrees mapSettings = self.iface.mapCanvas().mapSettings() if QGis.QGIS_VERSION_INT >= 20300 else self.iface.mapCanvas().mapRenderer() if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]: self.showMessageBar("The unit of current CRS is degrees, so terrain may not appear well.", QgsMessageBar.WARNING) self.templateType = templateType def currentObjectChanged(self, currentItem, previousItem): # save properties of previous item if previousItem and self.currentPage: self.saveProperties(previousItem, self.currentPage) self.currentItem = currentItem self.currentPage = None # hide text browser and all pages self.ui.textBrowser.hide() for page in self.pages.itervalues(): page.hide() parent = currentItem.parent() if parent is None: topItemIndex = currentItem.data(0, Qt.UserRole) pageType = self.topItemPages.get(topItemIndex, ppages.PAGE_NONE) page = self.pages.get(pageType, None) if page is None: self.showDescription(topItemIndex) return page.setup(self._settings.get(topItemIndex)) page.show() else: parentId = parent.data(0, Qt.UserRole) layerId = currentItem.data(0, Qt.UserRole) layer = QgsMapLayerRegistry.instance().mapLayer(unicode(layerId)) if layer is None: return layerType = layer.type() if layerType == QgsMapLayer.RasterLayer: page = self.pages[ppages.PAGE_DEM] page.setup(self._settings.get(parentId, {}).get(layerId, None), layer, False) elif layerType == QgsMapLayer.VectorLayer: page = self.pages[ppages.PAGE_VECTOR] page.setup(self._settings.get(parentId, {}).get(layerId, None), layer) else: return page.show() self.currentPage = page def objectItemChanged(self, item, column): parent = item.parent() if parent is None: return # checkbox of optional layer checked/unchecked if item == self.currentItem: if self.currentPage: # update enablement of property widgets self.currentPage.itemChanged(item) else: # select changed item self.ui.treeWidget.setCurrentItem(item) # set visible property #visible = item.data(0, Qt.CheckStateRole) == Qt.Checked #parentId = parent.data(0, Qt.UserRole) #layerId = item.data(0, Qt.UserRole) #self._settings.get(parentId, {}).get(layerId, {})["visible"] = visible def primaryDEMChanged(self, layerId): tree = self.ui.treeWidget parent = tree.topLevelItem(ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_OPTDEM)) tree.blockSignals(True) for i in range(parent.childCount()): item = parent.child(i) isPrimary = item.data(0, Qt.UserRole) == layerId item.setDisabled(isPrimary) tree.blockSignals(False) def showDescription(self, topItemIndex): fragment = {ObjectTreeItem.ITEM_OPTDEM: "additional-dem", ObjectTreeItem.ITEM_POINT: "point", ObjectTreeItem.ITEM_LINE: "line", ObjectTreeItem.ITEM_POLYGON: "polygon"}.get(topItemIndex) url = "http://qgis2threejs.readthedocs.org/en/docs-release/ExportSettings.html" if fragment: url += "#" + fragment html = '<a href="{0}">Online Help</a> about this item'.format(url) self.ui.textBrowser.setHtml(html) self.ui.textBrowser.show() def numericFields(self, layer): # get attributes of a sample feature and create numeric field name list numeric_fields = [] f = QgsFeature() layer.getFeatures().nextFeature(f) for field in f.fields(): isNumeric = False try: float(f.attribute(field.name())) isNumeric = True except ValueError: pass if isNumeric: numeric_fields.append(field.name()) return numeric_fields def mapTo3d(self): canvas = self.iface.mapCanvas() mapSettings = canvas.mapSettings() if QGis.QGIS_VERSION_INT >= 20300 else canvas.mapRenderer() world = self._settings.get(ObjectTreeItem.ITEM_WORLD, {}) bs = float(world.get("lineEdit_BaseSize", def_vals.baseSize)) ve = float(world.get("lineEdit_zFactor", def_vals.zExaggeration)) vs = float(world.get("lineEdit_zShift", def_vals.zShift)) return MapTo3D(mapSettings, bs, ve, vs) def progress(self, percentage=None, statusMsg=None): ui = self.ui if percentage is not None: ui.progressBar.setValue(percentage) if percentage == 100: ui.progressBar.setVisible(False) ui.label_Status.setText("") else: ui.progressBar.setVisible(True) if statusMsg is not None: ui.label_Status.setText(statusMsg) ui.label_Status.repaint() QgsApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def run(self): self.endPointSelection() ui = self.ui filename = ui.lineEdit_OutputFilename.text() # ""=Temporary file if filename and os.path.exists(filename): if QMessageBox.question(self, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok: return # export to web (three.js) export_settings = ExportSettings(self.pluginManager, self.localBrowsingMode) export_settings.loadSettings(self.settings()) export_settings.setMapCanvas(self.iface.mapCanvas()) err_msg = export_settings.checkValidity() if err_msg is not None: QMessageBox.warning(self, "Qgis2threejs", err_msg or "Invalid settings") return ui.pushButton_Run.setEnabled(False) ui.toolButton_Settings.setVisible(False) self.clearMessageBar() self.progress(0) if export_settings.exportMode == ExportSettings.PLAIN_MULTI_RES: # update quads and point on map canvas self.createRubberBands(export_settings.baseExtent, export_settings.quadtree()) # export ret = exportToThreeJS(export_settings, self.iface.legendInterface(), self.objectTypeManager, self.progress) self.progress(100) ui.pushButton_Run.setEnabled(True) if not ret: ui.toolButton_Settings.setVisible(True) return self.clearRubberBands() # store last selections settings = QSettings() settings.setValue("/Qgis2threejs/lastTemplate", export_settings.templatePath) settings.setValue("/Qgis2threejs/lastControls", export_settings.controls) # open web browser if not tools.openHTMLFile(export_settings.htmlfilename): ui.toolButton_Settings.setVisible(True) return # close dialog QDialog.accept(self) def reject(self): # save properties of current object item = self.ui.treeWidget.currentItem() if item and self.currentPage: self.saveProperties(item, self.currentPage) self.endPointSelection() self.clearRubberBands() QDialog.reject(self) def help(self): url = "http://qgis2threejs.readthedocs.org/" import webbrowser webbrowser.open(url, new=2) # new=2: new tab if possible def startPointSelection(self): canvas = self.iface.mapCanvas() if self.previousMapTool != self.mapTool: self.previousMapTool = canvas.mapTool() canvas.setMapTool(self.mapTool) self.pages[ppages.PAGE_DEM].toolButton_PointTool.setVisible(False) def endPointSelection(self): self.mapTool.reset() if self.previousMapTool is not None: self.iface.mapCanvas().setMapTool(self.previousMapTool) def mapToolSet(self, mapTool): return #TODO: unstable if mapTool != self.mapTool and self.currentPage is not None: if self.currentPage.pageType == ppages.PAGE_DEM and self.currentPage.isPrimary: self.currentPage.toolButton_PointTool.setVisible(True) def createRubberBands(self, baseExtent, quadtree): self.clearRubberBands() # create quads with rubber band self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line) self.rb_quads.setColor(Qt.blue) self.rb_quads.setWidth(1) quads = quadtree.quads() for quad in quads: geom = baseExtent.subrectangle(quad.rect).geometry() self.rb_quads.addGeometry(geom, None) self.log("Quad count: %d" % len(quads)) if not quadtree.focusRect: return # create a point with rubber band if quadtree.focusRect.width() == 0 or quadtree.focusRect.height() == 0: npt = quadtree.focusRect.center() self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rb_point.setColor(Qt.red) self.rb_point.addPoint(baseExtent.point(npt)) def clearRubberBands(self): # clear quads and point if self.rb_quads: self.iface.mapCanvas().scene().removeItem(self.rb_quads) self.rb_quads = None if self.rb_point: self.iface.mapCanvas().scene().removeItem(self.rb_point) self.rb_point = None def browseClicked(self): directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0] if not directory: directory = QDir.homePath() filename = QFileDialog.getSaveFileName(self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite) if not filename: return # append .html extension if filename doesn't have either .html or .htm if filename[-5:].lower() != ".html" and filename[-4:].lower() != ".htm": filename += ".html" self.ui.lineEdit_OutputFilename.setText(filename) def log(self, msg): if debug_mode: qDebug(msg)
class SimpleIntersectionMapTool(QgsMapTool): def __init__(self, iface): self.iface = iface self.mapCanvas = iface.mapCanvas() QgsMapTool.__init__(self, self.mapCanvas) self.settings = MySettings() self.rubber = QgsRubberBand(self.mapCanvas) def deactivate(self): self.rubber.reset() self.mapCanvas.layersChanged.disconnect(self.updateSnapperList) self.mapCanvas.scaleChanged.disconnect(self.updateSnapperList) QgsMapTool.deactivate(self) def activate(self): QgsMapTool.activate(self) self.rubber.setWidth(self.settings.value("rubberWidth")) self.rubber.setColor(self.settings.value("rubberColor")) self.updateSnapperList() self.mapCanvas.layersChanged.connect(self.updateSnapperList) self.mapCanvas.scaleChanged.connect(self.updateSnapperList) self.checkLayer() def updateSnapperList(self, dummy=None): # make a snapper list of all line and polygons layers self.snapperList = [] scale = self.iface.mapCanvas().mapRenderer().scale() for layer in self.mapCanvas.layers(): if layer.type() == QgsMapLayer.VectorLayer and layer.hasGeometryType(): if layer.geometryType() in (QGis.Line, QGis.Polygon): if not layer.hasScaleBasedVisibility() or layer.minimumScale() < scale <= layer.maximumScale(): snapLayer = QgsSnapper.SnapLayer() snapLayer.mLayer = layer snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment snapLayer.mTolerance = self.settings.value("selectTolerance") if self.settings.value("selectUnits") == "map": snapLayer.mUnitType = QgsTolerance.MapUnits else: snapLayer.mUnitType = QgsTolerance.Pixels self.snapperList.append(snapLayer) def canvasMoveEvent(self, mouseEvent): # put the observations within tolerance in the rubber band self.rubber.reset() for f in self.getFeatures(mouseEvent.pos()): self.rubber.addGeometry(f.geometry(), None) def canvasPressEvent(self, mouseEvent): self.rubber.reset() pos = mouseEvent.pos() features = self.getFeatures(pos) nFeat = len(features) if nFeat < 2: layerNames = " , ".join([feature.layer.name() for feature in features]) self.iface.messageBar().pushMessage("Intersect It", "You need 2 features to proceed a simple intersection." " %u given (%s)" % (nFeat, layerNames), QgsMessageBar.WARNING, 3) return intersectionP = self.intersection(features, pos) if intersectionP == QgsPoint(0,0): self.iface.messageBar().pushMessage("Intersect It", "Objects do not intersect.", QgsMessageBar.WARNING, 2) return layer = self.checkLayer() if layer is None: return f = QgsFeature() initFields = layer.dataProvider().fields() f.setFields(initFields) f.initAttributes(initFields.size()) f.setGeometry(QgsGeometry().fromPoint(intersectionP)) layer.editBuffer().addFeature(f) layer.triggerRepaint() def getFeatures(self, pixPoint): # do the snapping snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers(self.snapperList) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) # output snapped features features = [] alreadyGot = [] for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if (result.layer.id(), featureId) not in alreadyGot: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is False: continue if not isFeatureRendered(self.mapCanvas, result.layer, f): continue features.append(QgsFeature(f)) features[-1].layer = result.layer alreadyGot.append((result.layer.id(), featureId)) return features def intersection(self, features, pos): # try all the combinations nFeat = len(features) intersections = [] for i in range(nFeat-1): for j in range(i+1,nFeat): intersection = features[i].geometry().intersection(features[j].geometry()) intersectionMP = intersection.asMultiPoint() intersectionP = intersection.asPoint() if len(intersectionMP) == 0: intersectionMP = intersection.asPolyline() if len(intersectionMP) == 0 and intersectionP == QgsPoint(0, 0): continue if len(intersectionMP) > 1: mousePoint = self.toMapCoordinates(pos) intersectionP = intersectionMP[0] for point in intersectionMP[1:]: if mousePoint.sqrDist(point) < mousePoint.sqrDist(intersectionP): intersectionP = QgsPoint(point.x(), point.y()) if intersectionP != QgsPoint(0,0): intersections.append(intersectionP) if len(intersections) == 0: return QgsPoint(0,0) intersectionP = intersections[0] for point in intersections[1:]: if mousePoint.sqrDist(point) < mousePoint.sqrDist(intersectionP): intersectionP = QgsPoint(point.x(), point.y()) return intersectionP def checkLayer(self): # check output layer is defined layerid = self.settings.value("simpleIntersectionLayer") layer = QgsMapLayerRegistry.instance().mapLayer(layerid) if not self.settings.value("simpleIntersectionWritePoint") or layer is None: self.iface.messageBar().pushMessage("Intersect It", "You must define an output layer for simple intersections", QgsMessageBar.WARNING, 3) self.mapCanvas.unsetMapTool(self) return None if not layer.isEditable(): self.iface.messageBar().pushMessage("Intersect It", "The output layer <b>%s must be editable</b>" % layer.name(), QgsMessageBar.WARNING, 3) self.mapCanvas.unsetMapTool(self) return None return layer
class FinderBox(QComboBox): running = False toFinish = 0 searchStarted = pyqtSignal() searchFinished = pyqtSignal() def __init__(self, finders, iface, parent=None): self.iface = iface self.mapCanvas = iface.mapCanvas() self.rubber = QgsRubberBand(self.mapCanvas) self.rubber.setColor(QColor(255, 255, 50, 200)) self.rubber.setIcon(self.rubber.ICON_CIRCLE) self.rubber.setIconSize(15) self.rubber.setWidth(4) self.rubber.setBrushStyle(Qt.NoBrush) QComboBox.__init__(self, parent) self.setEditable(True) self.setInsertPolicy(QComboBox.InsertAtTop) self.setMinimumHeight(27) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.insertSeparator(0) self.lineEdit().returnPressed.connect(self.search) self.resultView = QTreeView() self.resultView.setHeaderHidden(True) self.resultView.setMinimumHeight(300) self.resultView.activated.connect(self.itemActivated) self.resultView.pressed.connect(self.itemPressed) self.setView(self.resultView) self.resultModel = ResultModel(self) self.setModel(self.resultModel) self.finders = finders for finder in self.finders.values(): finder.resultFound.connect(self.resultFound) finder.limitReached.connect(self.limitReached) finder.finished.connect(self.finished) self.clearButton = QPushButton(self) self.clearButton.setIcon( QIcon(":/plugins/quickfinder/icons/draft.svg")) self.clearButton.setText('') self.clearButton.setFlat(True) self.clearButton.setCursor(QCursor(Qt.ArrowCursor)) self.clearButton.setStyleSheet('border: 0px; padding: 0px;') self.clearButton.clicked.connect(self.clear) layout = QHBoxLayout(self) self.setLayout(layout) layout.addStretch() layout.addWidget(self.clearButton) layout.addSpacing(20) buttonSize = self.clearButton.sizeHint() # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth) padding = buttonSize.width() # + frameWidth + 1 self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' % padding) def __del__(self): if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber def clearSelection(self): self.resultModel.setSelected(None, self.resultView.palette()) self.rubber.reset() def clear(self): self.clearSelection() self.resultModel.clearResults() self.lineEdit().setText('') def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.clearSelection() QComboBox.keyPressEvent(self, event) def search(self): if self.running: return toFind = self.lineEdit().text() if not toFind or toFind == '': return self.running = True self.searchStarted.emit() self.clearSelection() self.resultModel.clearResults() self.resultModel.truncateHistory(MySettings().value("historyLength")) self.resultModel.setLoading(True) self.showPopup() QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.findersToStart = [] for finder in self.finders.values(): if finder.activated(): self.findersToStart.append(finder) bbox = self.mapCanvas.fullExtent() while len(self.findersToStart) > 0: finder = self.findersToStart[0] self.findersToStart.remove(finder) self.resultModel.addResult(finder.name) finder.start(toFind, bbox=bbox) # For case there is no finder activated self.finished(None) def stop(self): self.findersToStart = [] for finder in self.finders.values(): if finder.isRunning(): finder.stop() self.finished(None) def resultFound(self, finder, layername, value, geometry, srid): self.resultModel.addResult(finder.name, layername, value, geometry, srid) self.resultView.expandAll() def limitReached(self, finder, layername): self.resultModel.addEllipsys(finder.name, layername) def finished(self, finder): if len(self.findersToStart) > 0: return for finder in self.finders.values(): if finder.isRunning(): return self.running = False self.searchFinished.emit() self.resultModel.setLoading(False) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def itemActivated(self, index): item = self.resultModel.itemFromIndex(index) self.showItem(item) def itemPressed(self, index): item = self.resultModel.itemFromIndex(index) if QApplication.mouseButtons() == Qt.LeftButton: self.showItem(item) def showItem(self, item): if isinstance(item, ResultItem): self.resultModel.setSelected(item, self.resultView.palette()) geometry = self.transformGeom(item) self.rubber.reset(geometry.type()) self.rubber.setToGeometry(geometry, None) self.zoomToRubberBand() return if isinstance(item, GroupItem): child = item.child(0) if isinstance(child, ResultItem): self.resultModel.setSelected(item, self.resultView.palette()) self.rubber.reset(child.geometry.type()) for i in xrange(0, item.rowCount()): geometry = self.transformGeom(item.child(i)) self.rubber.addGeometry(geometry, None) self.zoomToRubberBand() return if item.__class__.__name__ == 'QStandardItem': self.clearSelection() def transformGeom(self, item): src_crs = QgsCoordinateReferenceSystem() src_crs.createFromSrid(item.srid) dest_crs = self.mapCanvas.mapRenderer().destinationCrs() geom = QgsGeometry(item.geometry) geom.transform(QgsCoordinateTransform(src_crs, dest_crs)) return geom def zoomToRubberBand(self): geom = self.rubber.asGeometry() if geom: rect = geom.boundingBox() rect.scale(1.5) self.mapCanvas.setExtent(rect) self.mapCanvas.refresh()
class MoveTool(QgsMapTool): def __init__(self, iface): """ Constructor :param iface: interface """ QgsMapTool.__init__(self, iface.mapCanvas()) self.__iface = iface self.__canvas = iface.mapCanvas() self.__icon_path = ':/plugins/VDLTools/icons/move_icon.png' self.__text = QCoreApplication.translate("VDLTools", "Move/Copy a feature") self.setCursor(Qt.ArrowCursor) self.__isEditing = 0 self.__findVertex = 0 self.__onMove = 0 self.__counter = 0 self.__layer = None self.__confDlg = None self.__lastFeatureId = None self.__selectedFeature = None self.__rubberBand = None self.__rubberSnap = None self.__newFeature = None self.__selectedVertex = None self.__layerConfig = None def icon_path(self): """ To get the icon path :return: icon path """ return self.__icon_path def text(self): """ To get the menu text :return: menu text """ return self.__text def toolName(self): """ To get the tool name :return: tool name """ return QCoreApplication.translate("VDLTools", "Move/Copy") def activate(self): """ When the action is selected """ QgsMapTool.activate(self) self.__updateList() self.__canvas.layersChanged.connect(self.__updateList) QgsProject.instance().snapSettingsChanged.connect(self.__updateList) def deactivate(self): """ When the action is deselected """ self.__canvas.layersChanged.disconnect(self.__updateList) QgsProject.instance().snapSettingsChanged.disconnect(self.__updateList) QgsMapTool.deactivate(self) def startEditing(self): """ To set the action as enable, as the layer is editable """ self.action().setEnabled(True) self.__layer.editingStarted.disconnect(self.startEditing) self.__layer.editingStopped.connect(self.stopEditing) def stopEditing(self): """ To set the action as disable, as the layer is not editable """ self.action().setEnabled(False) self.__layer.editingStopped.disconnect(self.stopEditing) self.__layer.editingStarted.connect(self.startEditing) if self.__canvas.mapTool == self: self.__iface.actionPan().trigger() def setTool(self): """ To set the current tool as this one """ self.__canvas.setMapTool(self) def removeLayer(self): """ To remove the current working layer """ if self.__layer is not None: if self.__layer.isEditable(): self.__layer.editingStopped.disconnect(self.stopEditing) else: self.__layer.editingStarted.disconnect(self.startEditing) self.__layer = None def setEnable(self, layer): """ To check if we can enable the action for the selected layer :param layer: selected layer """ if layer is not None\ and isinstance(layer, QgsVectorLayer): if layer == self.__layer: return if self.__layer is not None: if self.__layer.isEditable(): self.__layer.editingStopped.disconnect(self.stopEditing) else: self.__layer.editingStarted.disconnect(self.startEditing) self.__layer = layer if self.__layer.isEditable(): self.action().setEnabled(True) self.__updateList() self.__layer.editingStopped.connect(self.stopEditing) else: self.action().setEnabled(False) self.__layer.editingStarted.connect(self.startEditing) if self.__canvas.mapTool == self: self.__iface.actionPan().trigger() return self.action().setEnabled(False) self.removeLayer() def __pointPreview(self, point): """ To create a point geometry preview (rubberBand) :param point: new position as mapPoint """ point_v2 = GeometryV2.asPointV2(self.__selectedFeature.geometry()) self.__newFeature = QgsPointV2(point.x(), point.y()) self.__newFeature.addZValue(point_v2.z()) self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Point) self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.clone()), None) def __linePreview(self, point): """ To create a line geometry preview (rubberBand) :param point: new position as mapPoint """ line_v2, curved = GeometryV2.asLineV2( self.__selectedFeature.geometry()) print(self.__selectedVertex) vertex = QgsPointV2() line_v2.pointAt(self.__selectedVertex, vertex) self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Line) dx = vertex.x() - point.x() dy = vertex.y() - point.y() if isinstance(curved, (list, tuple)): self.__newFeature = QgsCompoundCurveV2() for pos in xrange(line_v2.nCurves()): curve_v2 = self.__newCurve(curved[pos], line_v2.curveAt(pos), dx, dy) self.__newFeature.addCurve(curve_v2) if pos == 0: self.__rubberBand.setToGeometry( QgsGeometry(curve_v2.curveToLine()), None) else: self.__rubberBand.addGeometry( QgsGeometry(curve_v2.curveToLine()), None) else: self.__newFeature = self.__newCurve(curved, line_v2, dx, dy) self.__rubberBand.setToGeometry( QgsGeometry(self.__newFeature.curveToLine()), None) def __newCurve(self, curved, line_v2, dx, dy): if curved: newCurve = QgsCircularStringV2() else: newCurve = QgsLineStringV2() points = [] for pos in xrange(line_v2.numPoints()): x = line_v2.pointN(pos).x() - dx y = line_v2.pointN(pos).y() - dy pt = QgsPointV2(x, y) pt.addZValue(line_v2.pointN(pos).z()) points.append(pt) newCurve.setPoints(points) return newCurve def __polygonPreview(self, point): """ To create a polygon geometry preview (rubberBand) :param point: new position as mapPoint """ polygon_v2, curved = GeometryV2.asPolygonV2( self.__selectedFeature.geometry()) vertex = polygon_v2.vertexAt(self.__polygonVertexId(polygon_v2)) dx = vertex.x() - point.x() dy = vertex.y() - point.y() self.__newFeature = QgsCurvePolygonV2() self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Line) line_v2 = self.__newLine(polygon_v2.exteriorRing(), dx, dy, curved[0]) self.__newFeature.setExteriorRing(line_v2) self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()), None) for num in xrange(polygon_v2.numInteriorRings()): line_v2 = self.__newLine(polygon_v2.interiorRing(num), dx, dy, curved[num + 1]) self.__newFeature.addInteriorRing(line_v2) self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()), None) def __polygonVertexId(self, polygon_v2): """ To get the id of the selected vertex from a polygon :param polygon_v2: the polygon as polygonV2 :return: id as QgsVertexId """ eR = polygon_v2.exteriorRing() if self.__selectedVertex < eR.numPoints(): return QgsVertexId(0, 0, self.__selectedVertex, 1) else: sel = self.__selectedVertex - eR.numPoints() for num in xrange(polygon_v2.numInteriorRings()): iR = polygon_v2.interiorRing(num) if sel < iR.numPoints(): return QgsVertexId(0, num + 1, sel, 1) sel -= iR.numPoints() def __newLine(self, curve_v2, dx, dy, curved): """ To create a new moved line for a part of a polygon :param curve_v2: the original line :param dx: x translation :param dy: y translation :return: the line as lineV2 """ if curved: new_line_v2 = QgsCircularStringV2() else: new_line_v2 = QgsLineStringV2() points = [] for pos in xrange(curve_v2.numPoints()): x = curve_v2.pointN(pos).x() - dx y = curve_v2.pointN(pos).y() - dy pt = QgsPointV2(x, y) pt.addZValue(curve_v2.pointN(pos).z()) points.append(pt) new_line_v2.setPoints(points) return new_line_v2 def __onConfirmClose(self): """ When the Cancel button in Move Confirm Dialog is pushed """ self.__confDlg.close() self.__rubberBand.reset() self.__rubberSnap.reset() self.__isEditing = 0 self.__lastFeatureId = None self.__selectedFeature = None self.__rubberBand = None self.__rubberSnap = None self.__newFeature = None self.__selectedVertex = None self.__layer.removeSelection() def __onConfirmMove(self): """ When the Move button in Move Confirm Dialog is pushed """ geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "Error"), QCoreApplication.translate("VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL) self.__layer.changeGeometry(self.__selectedFeature.id(), geometry) self.__layer.updateExtents() self.__onConfirmClose() def __onConfirmCopy(self): """ When the Copy button in Move Confirm Dialog is pushed """ geometry = QgsGeometry(self.__newFeature) if not geometry.isGeosValid(): self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "Error"), QCoreApplication.translate("VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL) feature = QgsFeature(self.__layer.pendingFields()) feature.setGeometry(geometry) primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn() for field in self.__selectedFeature.fields(): if field.name() != primaryKey: feature.setAttribute( field.name(), self.__selectedFeature.attribute(field.name())) if len(self.__selectedFeature.fields()) > 0 and \ self.__layer.editFormConfig().suppress() != QgsEditFormConfig.SuppressOn: self.__iface.openFeatureForm(self.__layer, feature) else: self.__layer.addFeature(feature) self.__layer.updateExtents() self.__onConfirmClose() def __updateList(self): """ To update the snapping options of the layer """ noUse, enabled, snappingType, unitType, tolerance, avoidIntersection = \ QgsProject.instance().snapSettingsForLayer(self.__layer.id()) self.__layerConfig = QgsSnappingUtils.LayerConfig( self.__layer, QgsPointLocator.Vertex, tolerance, unitType) if not enabled or tolerance == 0: self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "Error"), QCoreApplication.translate( "VDLTools", "This layer has no snapping options"), level=QgsMessageBar.CRITICAL) def canvasMoveEvent(self, event): """ When the mouse is moved :param event: mouse event """ if not self.__isEditing and not self.__findVertex and not self.__onMove: f = Finder.findClosestFeatureAt(event.mapPoint(), self.__layerConfig, self) if f is not None and self.__lastFeatureId != f.id(): self.__lastFeatureId = f.id() self.__layer.setSelectedFeatures([f.id()]) if f is None: self.__layer.removeSelection() self.__lastFeatureId = None elif self.__findVertex: self.__rubberBand.reset() closest = self.__selectedFeature.geometry().closestVertex( event.mapPoint()) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) self.__rubberBand.setToGeometry( QgsGeometry().fromPoint(closest[0]), None) elif self.__onMove: if self.__rubberBand: self.__rubberBand.reset() if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(event.mapPoint()) elif self.__layer.geometryType() == QGis.Line: self.__linePreview(event.mapPoint()) else: self.__pointPreview(event.mapPoint()) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) self.__rubberBand.setWidth(2) if self.__layer.geometryType() != QGis.Point: self.__rubberBand.setLineStyle(Qt.DotLine) else: self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) if self.__rubberSnap: self.__rubberSnap.reset() else: self.__rubberSnap = QgsRubberBand(self.__canvas, QGis.Point) self.__rubberSnap.setColor(color) self.__rubberSnap.setWidth(2) self.__rubberSnap.setIconSize(20) match = Finder.snap(event.mapPoint(), self.__canvas, True) if match.hasVertex(): if match.layer(): self.__rubberSnap.setIcon(4) self.__rubberSnap.setToGeometry( QgsGeometry().fromPoint(match.point()), None) else: self.__rubberSnap.setIcon(1) self.__rubberSnap.setToGeometry( QgsGeometry().fromPoint(match.point()), None) if match.hasEdge(): self.__rubberSnap.setIcon(3) self.__rubberSnap.setToGeometry( QgsGeometry().fromPoint(match.point()), None) # if self.__counter > 2: # if self.__rubberSnap: # self.__rubberSnap.reset() # else: # self.__rubberSnap = QgsRubberBand(self.__canvas, QGis.Point) # self.__rubberSnap.setColor(color) # self.__rubberSnap.setWidth(2) # self.__rubberSnap.setIconSize(20) # snappedIntersection = Finder.snapToIntersection(event.mapPoint(), self, self.__layerList) # if snappedIntersection is None: # snappedPoint = Finder.snapToLayers(event.mapPoint(), self.__snapperList) # if snappedPoint is not None: # self.__rubberSnap.setIcon(4) # self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(snappedPoint), None) # else: # self.__rubberSnap.setIcon(1) # self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(snappedIntersection), None) # self.__counter = 0 # else: # self.__counter += 1 def canvasReleaseEvent(self, event): """ When the mouse is clicked :param event: mouse event """ if not self.__isEditing and not self.__findVertex and not self.__onMove: found_features = self.__layer.selectedFeatures() if len(found_features) > 0: if len(found_features) < 1: self.__iface.messageBar().pushMessage( QCoreApplication.translate("VDLTools", "One feature at a time"), level=QgsMessageBar.INFO) return self.__selectedFeature = found_features[0] if self.__layer.geometryType() != QGis.Point: self.__findVertex = 1 self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Point) else: self.__onMove = 1 # self.__snapperList, self.__layerList = Finder.updateSnapperList(self.__iface) elif self.__findVertex: self.__findVertex = 0 closest = self.__selectedFeature.geometry().closestVertex( event.mapPoint()) self.__selectedVertex = closest[1] self.__onMove = 1 # self.__snapperList, self.__layerList = Finder.updateSnapperList(self.__iface) elif self.__onMove: self.__onMove = 0 mapPoint = event.mapPoint() match = Finder.snap(event.mapPoint(), self.__canvas, True) if match.hasVertex() or match.hasEdge(): mapPoint = match.point() # snappedIntersection = Finder.snapToIntersection(event.mapPoint(), self, self.__layerList) # if snappedIntersection is None: # snappedPoint = Finder.snapToLayers(event.mapPoint(), self.__snapperList) # if snappedPoint is not None: # mapPoint = snappedPoint # else: # mapPoint = snappedIntersection self.__isEditing = 1 if self.__rubberBand: self.__rubberBand.reset() if self.__layer.geometryType() == QGis.Polygon: self.__polygonPreview(mapPoint) elif self.__layer.geometryType() == QGis.Line: self.__linePreview(mapPoint) else: self.__pointPreview(mapPoint) color = QColor("red") color.setAlphaF(0.78) self.__rubberBand.setColor(color) if self.__layer.geometryType() != QGis.Point: self.__rubberBand.setWidth(2) self.__rubberBand.setLineStyle(Qt.DotLine) else: self.__rubberBand.setIcon(4) self.__rubberBand.setIconSize(20) self.__confDlg = MoveConfirmDialog() self.__confDlg.moveButton().clicked.connect(self.__onConfirmMove) self.__confDlg.copyButton().clicked.connect(self.__onConfirmCopy) self.__confDlg.cancelButton().clicked.connect( self.__onConfirmClose) self.__confDlg.show()
class LatLonTools: digitizerDialog = None convertCoordinateDialog = None mapTool = None showMapTool = None copyExtentTool = None def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.crossRb.setColor(Qt.red) self.provider = LatLonToolsProvider() self.toolbar = self.iface.addToolBar('Lat Lon Tools Toolbar') self.toolbar.setObjectName('LatLonToolsToolbar') def initGui(self): '''Initialize Lot Lon Tools GUI.''' # Initialize the Settings Dialog box self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow()) # Add Interface for Coordinate Capturing icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.svg") self.copyAction = QAction(icon, "Copy/Display Coordinate", self.iface.mainWindow()) self.copyAction.setObjectName('latLonToolsCopy') self.copyAction.triggered.connect(self.startCapture) self.copyAction.setCheckable(True) self.toolbar.addAction(self.copyAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction) # Add Interface for External Map icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png") self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow()) self.externMapAction.setObjectName('latLonToolsExternalMap') self.externMapAction.triggered.connect(self.setShowMapTool) self.externMapAction.setCheckable(True) self.toolbar.addAction(self.externMapAction) self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction) # Add Interface for Zoom to Coordinate icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.svg") self.zoomToAction = QAction(icon, "Zoom To Coordinate", self.iface.mainWindow()) self.zoomToAction.setObjectName('latLonToolsZoom') self.zoomToAction.triggered.connect(self.showZoomToDialog) self.toolbar.addAction(self.zoomToAction) self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction) self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog) self.zoomToDialog.hide() # Add Interface for Multi point zoom icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.svg') self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow()) self.multiZoomToAction.setObjectName('latLonToolsMultiZoom') self.multiZoomToAction.triggered.connect(self.multiZoomTo) self.toolbar.addAction(self.multiZoomToAction) self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction) self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow()) self.multiZoomDialog.hide() self.multiZoomDialog.setFloating(True) menu = QMenu() # Add Interface for copying the canvas extent icon = QIcon(os.path.dirname(__file__) + "/images/copycanvas.svg") self.copyCanvasAction = menu.addAction(icon, 'Copy Canvas Extent', self.copyCanvas) self.copyCanvasAction.setObjectName('latLonToolsCopyCanvasExtent') # Add Interface for copying an interactive extent icon = QIcon(os.path.dirname(__file__) + "/images/copyextent.svg") self.copyExtentAction = menu.addAction(icon, 'Copy Selected Area Extent', self.copyExtent) self.copyExtentAction.setCheckable(True) self.copyExtentAction.setObjectName( 'latLonToolsCopySelectedAreaExtent') # Add Interface for copying a layer extent icon = QIcon(os.path.dirname(__file__) + "/images/copylayerextent.svg") self.copyLayerExtentAction = menu.addAction(icon, 'Copy Layer Extent', self.copyLayerExtent) self.copyLayerExtentAction.setObjectName('latLonToolsCopyLayerExtent') # Add Interface for copying the extent of selected features icon = QIcon( os.path.dirname(__file__) + "/images/copyselectedlayerextent.svg") self.copySelectedFeaturesExtentAction = menu.addAction( icon, 'Copy Selected Features Extent', self.copySelectedFeaturesExtent) self.copySelectedFeaturesExtentAction.setObjectName( 'latLonToolsCopySelectedFeaturesExtent') # Add the copy extent tools to the menu icon = QIcon(os.path.dirname(__file__) + '/images/copylayerextent.svg') self.copyExtentsAction = QAction(icon, 'Copy Extents to Clipboard', self.iface.mainWindow()) self.copyExtentsAction.setMenu(menu) self.iface.addPluginToMenu('Lat Lon Tools', self.copyExtentsAction) # Add the copy extent tools to the toolbar self.copyExtentButton = QToolButton() self.copyExtentButton.setMenu(menu) self.copyExtentButton.setDefaultAction(self.copyCanvasAction) self.copyExtentButton.setPopupMode(QToolButton.MenuButtonPopup) self.copyExtentButton.triggered.connect(self.copyExtentTriggered) self.copyExtentToolbar = self.toolbar.addWidget(self.copyExtentButton) # Create the coordinate converter menu icon = QIcon(':/images/themes/default/mIconProjectionEnabled.svg') self.convertCoordinatesAction = QAction(icon, "Coordinate Conversion", self.iface.mainWindow()) self.convertCoordinatesAction.setObjectName( 'latLonToolsCoordinateConversion') self.convertCoordinatesAction.triggered.connect( self.convertCoordinatesTool) self.toolbar.addAction(self.convertCoordinatesAction) self.iface.addPluginToMenu("Lat Lon Tools", self.convertCoordinatesAction) # Create the conversions menu menu = QMenu() icon = QIcon(os.path.dirname(__file__) + '/images/field2geom.svg') action = menu.addAction(icon, "Fields to point layer", self.field2geom) action.setObjectName('latLonToolsField2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.svg') action = menu.addAction(icon, "Point layer to fields", self.geom2Field) action.setObjectName('latLonToolsGeom2Field') icon = QIcon(os.path.dirname(__file__) + '/images/pluscodes.svg') action = menu.addAction(icon, "Plus Codes to point layer", self.PlusCodestoLayer) action.setObjectName('latLonToolsPlusCodes2Geom') action = menu.addAction(icon, "Point layer to Plus Codes", self.toPlusCodes) action.setObjectName('latLonToolsGeom2PlusCodes') icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.svg') action = menu.addAction(icon, "MGRS to point layer", self.MGRStoLayer) action.setObjectName('latLonToolsMGRS2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.svg') action = menu.addAction(icon, "Point layer to MGRS", self.toMGRS) action.setObjectName('latLonToolsGeom2MGRS') self.conversionsAction = QAction(icon, "Conversions", self.iface.mainWindow()) self.conversionsAction.setMenu(menu) self.iface.addPluginToMenu('Lat Lon Tools', self.conversionsAction) # Add to Digitize Toolbar icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.svg') self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow()) self.digitizeAction.setObjectName('latLonToolsDigitize') self.digitizeAction.triggered.connect(self.digitizeClicked) self.digitizeAction.setEnabled(False) self.toolbar.addAction(self.digitizeAction) self.iface.addPluginToMenu('Lat Lon Tools', self.digitizeAction) # Initialize the Settings Dialog Box settingsicon = QIcon(':/images/themes/default/mActionOptions.svg') self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow()) self.settingsAction.setObjectName('latLonToolsSettings') self.settingsAction.setToolTip('Lat Lon Tools Settings') self.settingsAction.triggered.connect(self.settings) self.toolbar.addAction(self.settingsAction) self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction) # Help icon = QIcon(os.path.dirname(__file__) + '/images/help.svg') self.helpAction = QAction(icon, "Help", self.iface.mainWindow()) self.helpAction.setObjectName('latLonToolsHelp') self.helpAction.triggered.connect(self.help) self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction) self.iface.currentLayerChanged.connect(self.currentLayerChanged) self.canvas.mapToolSet.connect(self.resetTools) self.enableDigitizeTool() # Add the processing provider QgsApplication.processingRegistry().addProvider(self.provider) InitLatLonFunctions() def resetTools(self, newtool, oldtool): '''Uncheck the Copy Lat Lon tool''' try: if self.mapTool and (oldtool is self.mapTool): self.copyAction.setChecked(False) if self.showMapTool and (oldtool is self.showMapTool): self.externMapAction.setChecked(False) if newtool is self.mapTool: self.copyAction.setChecked(True) if newtool is self.showMapTool: self.externMapAction.setChecked(True) except Exception: pass def unload(self): '''Unload LatLonTools from the QGIS interface''' self.zoomToDialog.removeMarker() self.multiZoomDialog.removeMarkers() if self.mapTool: self.canvas.unsetMapTool(self.mapTool) if self.showMapTool: self.canvas.unsetMapTool(self.showMapTool) self.iface.removePluginMenu('Lat Lon Tools', self.copyAction) self.iface.removePluginMenu('Lat Lon Tools', self.copyExtentsAction) self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction) self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.convertCoordinatesAction) self.iface.removePluginMenu('Lat Lon Tools', self.conversionsAction) self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction) self.iface.removePluginMenu('Lat Lon Tools', self.helpAction) self.iface.removePluginMenu('Lat Lon Tools', self.digitizeAction) self.iface.removeDockWidget(self.zoomToDialog) self.iface.removeDockWidget(self.multiZoomDialog) # Remove Toolbar Icons self.iface.removeToolBarIcon(self.copyAction) self.iface.removeToolBarIcon(self.copyExtentToolbar) self.iface.removeToolBarIcon(self.zoomToAction) self.iface.removeToolBarIcon(self.externMapAction) self.iface.removeToolBarIcon(self.multiZoomToAction) self.iface.removeToolBarIcon(self.convertCoordinatesAction) self.iface.removeToolBarIcon(self.digitizeAction) del self.toolbar if self.convertCoordinateDialog: self.iface.removeDockWidget(self.convertCoordinateDialog) self.convertCoordinateDialog = None self.zoomToDialog = None self.multiZoomDialog = None self.settingsDialog = None self.showMapTool = None self.mapTool = None self.digitizerDialog = None QgsApplication.processingRegistry().removeProvider(self.provider) UnloadLatLonFunctions() def startCapture(self): '''Set the focus of the copy coordinate tool''' if self.mapTool is None: from .copyLatLonTool import CopyLatLonTool self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface) self.canvas.setMapTool(self.mapTool) def copyExtentTriggered(self, action): self.copyExtentButton.setDefaultAction(action) def copyExtent(self): if self.copyExtentTool is None: from .captureExtent import CaptureExtentTool self.copyExtentTool = CaptureExtentTool(self.iface, self) self.copyExtentTool.setAction(self.copyExtentAction) self.canvas.setMapTool(self.copyExtentTool) def copyLayerExtent(self): layer = self.iface.activeLayer() if not layer or not layer.isValid(): return if isinstance(layer, QgsVectorLayer) and (layer.featureCount() == 0): self.iface.messageBar().pushMessage( "", "This layer has no features - A bounding box cannot be calculated.", level=Qgis.Warning, duration=4) return src_crs = layer.crs() extent = layer.extent() if settings.bBoxCrs == 0: dst_crs = epsg4326 else: dst_crs = self.canvas.mapSettings().destinationCrs() outStr = getExtentString(extent, src_crs, dst_crs) clipboard = QApplication.clipboard() clipboard.setText(outStr) self.iface.messageBar().pushMessage( "", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4) def copySelectedFeaturesExtent(self): layer = self.iface.activeLayer() if not layer or not layer.isValid(): return if isinstance(layer, QgsVectorLayer) and (layer.featureCount() == 0): self.iface.messageBar().pushMessage( "", "This layer has no features - A bounding box cannot be calculated.", level=Qgis.Warning, duration=4) return if isinstance(layer, QgsVectorLayer): extent = layer.boundingBoxOfSelected() if extent.isNull(): self.iface.messageBar().pushMessage( "", "No features were selected.", level=Qgis.Warning, duration=4) return else: extent = layer.extent() src_crs = layer.crs() if settings.bBoxCrs == 0: dst_crs = epsg4326 else: dst_crs = self.canvas.mapSettings().destinationCrs() outStr = getExtentString(extent, src_crs, dst_crs) clipboard = QApplication.clipboard() clipboard.setText(outStr) self.iface.messageBar().pushMessage( "", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4) def copyCanvas(self): extent = self.iface.mapCanvas().extent() canvas_crs = self.canvas.mapSettings().destinationCrs() if settings.bBoxCrs == 0: dst_crs = epsg4326 else: dst_crs = canvas_crs outStr = getExtentString(extent, canvas_crs, dst_crs) clipboard = QApplication.clipboard() clipboard.setText(outStr) self.iface.messageBar().pushMessage( "", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4) def setShowMapTool(self): '''Set the focus of the external map tool.''' if self.showMapTool is None: from .showOnMapTool import ShowOnMapTool self.showMapTool = ShowOnMapTool(self.iface) self.canvas.setMapTool(self.showMapTool) def showZoomToDialog(self): '''Show the zoom to docked widget.''' self.zoomToDialog.show() def convertCoordinatesTool(self): '''Display the Convert Coordinate Tool Dialog box.''' if self.convertCoordinateDialog is None: from .coordinateConverter import CoordinateConverterWidget self.convertCoordinateDialog = CoordinateConverterWidget( self, self.settingsDialog, self.iface, self.iface.mainWindow()) self.convertCoordinateDialog.setFloating(True) self.iface.addDockWidget(Qt.RightDockWidgetArea, self.convertCoordinateDialog) self.convertCoordinateDialog.show() def multiZoomTo(self): '''Display the Multi-zoom to dialog box''' self.multiZoomDialog.show() def field2geom(self): '''Convert layer containing a point x & y coordinate to a new point layer''' processing.execAlgorithmDialog('latlontools:field2geom', {}) def geom2Field(self): '''Convert layer geometry to a text string''' processing.execAlgorithmDialog('latlontools:geom2field', {}) def toMGRS(self): '''Display the to MGRS dialog box''' processing.execAlgorithmDialog('latlontools:point2mgrs', {}) def MGRStoLayer(self): '''Display the to MGRS dialog box''' processing.execAlgorithmDialog('latlontools:mgrs2point', {}) def toPlusCodes(self): processing.execAlgorithmDialog('latlontools:point2pluscodes', {}) def PlusCodestoLayer(self): processing.execAlgorithmDialog('latlontools:pluscodes2point', {}) def settings(self): '''Show the settings dialog box''' self.settingsDialog.show() def help(self): '''Display a help page''' import webbrowser url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2) def settingsChanged(self): # Settings may have changed so we need to make sure the zoomToDialog window is configured properly self.zoomToDialog.configure() self.multiZoomDialog.settingsChanged() def zoomTo(self, src_crs, lat, lon): canvas_crs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(src_crs, canvas_crs, QgsProject.instance()) x, y = transform.transform(float(lon), float(lat)) rect = QgsRectangle(x, y, x, y) self.canvas.setExtent(rect) pt = QgsPointXY(x, y) self.highlight(pt) self.canvas.refresh() return pt def highlight(self, point): currExt = self.canvas.extent() leftPt = QgsPoint(currExt.xMinimum(), point.y()) rightPt = QgsPoint(currExt.xMaximum(), point.y()) topPt = QgsPoint(point.x(), currExt.yMaximum()) bottomPt = QgsPoint(point.x(), currExt.yMinimum()) horizLine = QgsGeometry.fromPolyline([leftPt, rightPt]) vertLine = QgsGeometry.fromPolyline([topPt, bottomPt]) self.crossRb.reset(QgsWkbTypes.LineGeometry) self.crossRb.addGeometry(horizLine, None) self.crossRb.addGeometry(vertLine, None) QTimer.singleShot(700, self.resetRubberbands) def resetRubberbands(self): self.crossRb.reset() def digitizeClicked(self): if self.digitizerDialog is None: from .digitizer import DigitizerWidget self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow()) self.digitizerDialog.show() def currentLayerChanged(self): layer = self.iface.activeLayer() if layer is not None: try: layer.editingStarted.disconnect(self.layerEditingChanged) except Exception: pass try: layer.editingStopped.disconnect(self.layerEditingChanged) except Exception: pass if isinstance(layer, QgsVectorLayer): layer.editingStarted.connect(self.layerEditingChanged) layer.editingStopped.connect(self.layerEditingChanged) self.enableDigitizeTool() def layerEditingChanged(self): self.enableDigitizeTool() def enableDigitizeTool(self): self.digitizeAction.setEnabled(False) layer = self.iface.activeLayer() if layer is not None and isinstance(layer, QgsVectorLayer) and ( layer.geometryType() == QgsWkbTypes.PointGeometry) and layer.isEditable(): self.digitizeAction.setEnabled(True) else: if self.digitizerDialog is not None: self.digitizerDialog.close()
class FinderBox(QComboBox): running = False to_finish = 0 search_started = pyqtSignal() search_finished = pyqtSignal() def __init__(self, finders, iface, parent=None): self.iface = iface self.mapCanvas = iface.mapCanvas() self.marker = None self.rubber = QgsRubberBand(self.mapCanvas) self.rubber.setColor(QColor(255, 255, 50, 200)) self.rubber.setIcon(self.rubber.ICON_CIRCLE) self.rubber.setIconSize(15) self.rubber.setWidth(4) self.rubber.setBrushStyle(Qt.NoBrush) QComboBox.__init__(self, parent) self.setEditable(True) self.setInsertPolicy(QComboBox.InsertAtTop) self.setMinimumHeight(27) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.insertSeparator(0) self.lineEdit().returnPressed.connect(self.search) self.result_view = QTreeView() self.result_view.setHeaderHidden(True) self.result_view.setMinimumHeight(300) self.result_view.activated.connect(self.itemActivated) self.result_view.pressed.connect(self.itemPressed) self.setView(self.result_view) self.result_model = ResultModel(self) self.setModel(self.result_model) self.finders = finders for finder in list(self.finders.values()): finder.result_found.connect(self.result_found) finder.limit_reached.connect(self.limit_reached) finder.finished.connect(self.finished) self.clearButton = QPushButton(self) self.clearButton.setIcon( QIcon(":/plugins/quickfinder/icons/draft.svg")) self.clearButton.setText('') self.clearButton.setFlat(True) self.clearButton.setCursor(QCursor(Qt.ArrowCursor)) self.clearButton.setStyleSheet('border: 0px; padding: 0px;') self.clearButton.clicked.connect(self.clear) layout = QHBoxLayout(self) self.setLayout(layout) layout.addStretch() layout.addWidget(self.clearButton) layout.addSpacing(20) button_size = self.clearButton.sizeHint() # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth) padding = button_size.width() # + frameWidth + 1 self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' % padding) def __del__(self): if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber #clear marker that the lonlat seted def clearMarker(self): self.mapCanvas.scene().removeItem(self.marker) self.marker = None def clearSelection(self): self.result_model.setSelected(None, self.result_view.palette()) self.rubber.reset() #self.clearMarker() def clear(self): self.clearSelection() self.result_model.clearResults() self.lineEdit().setText('') def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.clearSelection() self.clearMarker() QComboBox.keyPressEvent(self, event) def lnglatFinder(self, to_find): import re m = re.match(r'(?P<lon>-?\d*(.\d+))\s+(?P<lat>-?\d*(.\d+))', to_find) if not m: return False x = float(m.group('lon')) y = float(m.group('lat')) return self.zoomLnglat(x, y) def zoomLnglat(self, lng, lat): x, y = lng, lat canvas = self.mapCanvas currExt = canvas.extent() canvasCenter = currExt.center() dx = float(x) - canvasCenter.x() dy = float(y) - canvasCenter.y() xMin = currExt.xMinimum() + dx xMax = currExt.xMaximum() + dx yMin = currExt.yMinimum() + dy yMax = currExt.yMaximum() + dy rect = QgsRectangle(xMin, yMin, xMax, yMax) canvas.setExtent(rect) pt = QgsPointXY(float(x), float(y)) self.marker = QgsVertexMarker(canvas) self.marker.setCenter(pt) self.marker.setIconSize(18) self.marker.setPenWidth(2) self.marker.setIconType(QgsVertexMarker.ICON_CROSS) canvas.refresh() return True # def geocodeFinder(self, to_finder): # print(to_finder[:2]) # if not to_finder[:2] == 'b:': # return False # # address = to_finder[2:] # url = MySettings().value("baiduUrl") # url = url + parse.quote(address) # # response = request.urlopen(url) # content = response.read() # data = json.loads(content) # print(data) # lng, lat = (data['result']['location']['lng'], data['result']['location']['lat']) # from .cood_trans import bd09_to_wgs84 # lng, lat = bd09_to_wgs84(lng, lat) # print(f'{lng}-{lat}') # return self.zoomLnglat(lng, lat) def search(self): # self.geocode() if self.running: return to_find = self.lineEdit().text() if not to_find or to_find == '': return # if not (self.lnglatFinder(to_find) or self.geocodeFinder(to_find)): if not self.lnglatFinder(to_find): self.showPopup() self.running = True self.search_started.emit() self.clearSelection() self.result_model.clearResults() self.result_model.truncateHistory(MySettings().value("historyLength")) self.result_model.setLoading(True) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.finders_to_start = [] for finder in list(self.finders.values()): if finder.activated(): self.finders_to_start.append(finder) bbox = self.mapCanvas.fullExtent() while len(self.finders_to_start) > 0: finder = self.finders_to_start[0] self.finders_to_start.remove(finder) self.result_model.addResult(finder.name) finder.start(to_find, bbox=bbox) # For case there is no finder activated self.finished(None) def stop(self): self.finders_to_start = [] for finder in list(self.finders.values()): if finder.is_running(): finder.stop() self.finished(None) def result_found(self, finder, layername, value, geometry, srid): self.result_model.addResult(finder.name, layername, value, geometry, srid) self.result_view.expandAll() def limit_reached(self, finder, layername): self.result_model.addEllipsys(finder.name, layername) def finished(self, finder): if len(self.finders_to_start) > 0: return for finder in list(self.finders.values()): if finder.is_running(): return self.running = False self.search_finished.emit() self.result_model.setLoading(False) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def itemActivated(self, index): item = self.result_model.itemFromIndex(index) self.showItem(item) def itemPressed(self, index): item = self.result_model.itemFromIndex(index) if QApplication.mouseButtons() == Qt.LeftButton: self.showItem(item) def showItem(self, item): if isinstance(item, ResultItem): self.result_model.setSelected(item, self.result_view.palette()) geometry = self.transform_geom(item) self.rubber.reset(geometry.type()) self.rubber.setToGeometry(geometry, None) self.zoom_to_rubberband() return if isinstance(item, GroupItem): child = item.child(0) if isinstance(child, ResultItem): self.result_model.setSelected(item, self.result_view.palette()) self.rubber.reset(child.geometry.type()) for i in range(0, item.rowCount()): geometry = self.transform_geom(item.child(i)) self.rubber.addGeometry(geometry, None) self.zoom_to_rubberband() return if item.__class__.__name__ == 'QStandardItem': self.clearSelection() def transform_geom(self, item): src_crs = QgsCoordinateReferenceSystem() src_crs.createFromSrid(item.srid) dest_crs = self.mapCanvas.mapSettings().destinationCrs() geom = QgsGeometry(item.geometry) geom.transform( QgsCoordinateTransform(src_crs, dest_crs, QgsProject.instance())) return geom def zoom_to_rubberband(self): geom = self.rubber.asGeometry() if geom: rect = geom.boundingBox() rect.scale(1.5) self.mapCanvas.setExtent(rect) self.mapCanvas.refresh()
class FinderBox(QComboBox): running = False toFinish = 0 searchStarted = pyqtSignal() searchFinished = pyqtSignal() def __init__(self, finders, iface, parent=None): self.iface = iface self.mapCanvas = iface.mapCanvas() self.rubber = QgsRubberBand(self.mapCanvas) self.rubber.setColor(QColor(255, 255, 50, 200)) self.rubber.setIcon(self.rubber.ICON_CIRCLE) self.rubber.setIconSize(15) self.rubber.setWidth(4) self.rubber.setBrushStyle(Qt.NoBrush) QComboBox.__init__(self, parent) self.setEditable(True) self.setInsertPolicy(QComboBox.InsertAtTop) self.setMinimumHeight(27) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.insertSeparator(0) self.lineEdit().returnPressed.connect(self.search) self.resultView = QTreeView() self.resultView.setHeaderHidden(True) self.resultView.setMinimumHeight(300) self.resultView.activated.connect(self.itemActivated) self.resultView.pressed.connect(self.itemPressed) self.setView(self.resultView) self.resultModel = ResultModel(self) self.setModel(self.resultModel) self.finders = finders for finder in self.finders.values(): finder.resultFound.connect(self.resultFound) finder.limitReached.connect(self.limitReached) finder.finished.connect(self.finished) self.clearButton = QPushButton(self) self.clearButton.setIcon(QIcon(":/plugins/quickfinder/icons/draft.svg")) self.clearButton.setText('') self.clearButton.setFlat(True) self.clearButton.setCursor(QCursor(Qt.ArrowCursor)) self.clearButton.setStyleSheet('border: 0px; padding: 0px;') self.clearButton.clicked.connect(self.clear) layout = QHBoxLayout(self) self.setLayout(layout) layout.addStretch() layout.addWidget(self.clearButton) layout.addSpacing(20) buttonSize = self.clearButton.sizeHint() # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth) padding = buttonSize.width() # + frameWidth + 1 self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' % padding) def __del__(self): if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber def clearSelection(self): self.resultModel.setSelected(None, self.resultView.palette()) self.rubber.reset() def clear(self): self.clearSelection() self.resultModel.clearResults() self.lineEdit().setText('') def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.clearSelection() QComboBox.keyPressEvent(self, event) def search(self): if self.running: return toFind = self.lineEdit().text() if not toFind or toFind == '': return self.running = True self.searchStarted.emit() self.clearSelection() self.resultModel.clearResults() self.resultModel.truncateHistory(MySettings().value("historyLength")) self.resultModel.setLoading(True) self.showPopup() QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.findersToStart = [] for finder in self.finders.values(): if finder.activated(): self.findersToStart.append(finder) bbox = self.mapCanvas.fullExtent() while len(self.findersToStart) > 0: finder = self.findersToStart[0] self.findersToStart.remove(finder) self.resultModel.addResult(finder.name) finder.start(toFind, bbox=bbox) # For case there is no finder activated self.finished(None) def stop(self): self.findersToStart = [] for finder in self.finders.values(): if finder.isRunning(): finder.stop() self.finished(None) def resultFound(self, finder, layername, value, geometry, srid): self.resultModel.addResult(finder.name, layername, value, geometry, srid) self.resultView.expandAll() def limitReached(self, finder, layername): self.resultModel.addEllipsys(finder.name, layername) def finished(self, finder): if len(self.findersToStart) > 0: return for finder in self.finders.values(): if finder.isRunning(): return self.running = False self.searchFinished.emit() self.resultModel.setLoading(False) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def itemActivated(self, index): item = self.resultModel.itemFromIndex(index) self.showItem(item) def itemPressed(self, index): item = self.resultModel.itemFromIndex(index) if QApplication.mouseButtons() == Qt.LeftButton: self.showItem(item) def showItem(self, item): if isinstance(item, ResultItem): self.resultModel.setSelected(item, self.resultView.palette()) geometry = self.transformGeom(item) self.rubber.reset(geometry.type()) self.rubber.setToGeometry(geometry, None) self.zoomToRubberBand() return if isinstance(item, GroupItem): child = item.child(0) if isinstance(child, ResultItem): self.resultModel.setSelected(item, self.resultView.palette()) self.rubber.reset(child.geometry.type()) for i in xrange(0, item.rowCount()): geometry = self.transformGeom(item.child(i)) self.rubber.addGeometry(geometry, None) self.zoomToRubberBand() return if item.__class__.__name__ == 'QStandardItem': self.clearSelection() def transformGeom(self, item): src_crs = QgsCoordinateReferenceSystem() src_crs.createFromSrid(item.srid) dest_crs = self.mapCanvas.mapRenderer().destinationCrs() geom = QgsGeometry(item.geometry) geom.transform(QgsCoordinateTransform(src_crs, dest_crs)) return geom def zoomToRubberBand(self): geom = self.rubber.asGeometry() if geom: rect = geom.boundingBox() rect.scale(1.5) self.mapCanvas.setExtent(rect) self.mapCanvas.refresh()
class CopyTimeZoneTool(QgsMapToolEmitPoint): '''Class to interact with the map canvas to capture the coordinate when the mouse button is pressed and to display the coordinate in in the status bar.''' tzf = None last_tz = None def __init__(self, settings, iface): QgsMapToolEmitPoint.__init__(self, iface.mapCanvas()) self.iface = iface self.settings = settings self.canvas = iface.mapCanvas() # Set up a polygon rubber band self.rubber = QgsRubberBand(self.canvas) self.rubber.setColor(QColor(255, 70, 0, 200)) self.rubber.setWidth(3) self.rubber.setBrushStyle(Qt.NoBrush) def activate(self): '''When activated set the cursor to a crosshair.''' self.canvas.setCursor(Qt.CrossCursor) self.tzf = tzf_instance.getTZF() self.settings.show() def deactivate(self): self.last_tz = None self.rubber.reset() def formatMessage(self, pt): '''Format the coordinate string according to the settings from the settings dialog.''' # Make sure the coordinate is transformed to EPSG:4326 canvasCRS = self.canvas.mapSettings().destinationCrs() if canvasCRS == epsg4326: pt4326 = pt else: transform = QgsCoordinateTransform(canvasCRS, epsg4326, QgsProject.instance()) pt4326 = transform.transform(pt.x(), pt.y()) try: tz_name = self.tzf.timezone_at(lng=pt4326.x(), lat=pt4326.y()) mode = self.settings.mode() if mode == 1: msg = tz_name else: tz = timezone(tz_name) date = self.settings.date() loc_dt = tz.localize( datetime(date.year(), date.month(), date.day())) offset = loc_dt.strftime('%z') if mode == 0: msg = '{} ({})'.format(tz_name, offset) else: msg = offset except Exception: # traceback.print_exc() msg = '' tz_name = '' if not msg: msg = '' tz_name = '' return (tz_name, msg) def canvasMoveEvent(self, event): '''Capture the coordinate as the user moves the mouse over the canvas. Show it in the status bar.''' pt = event.mapPoint() tz_name, msg = self.formatMessage(pt) if tz_name != self.last_tz: self.rubber.reset() self.last_tz = tz_name if tz_name != '': polygon = self.tzf.get_geometry(tz_name=tz_name) qgs_poly = tzf_to_qgis_polygon(polygon) canvasCRS = self.canvas.mapSettings().destinationCrs() if epsg4326 != canvasCRS: to_canvas_crs = QgsCoordinateTransform( epsg4326, canvasCRS, QgsProject.instance()) qgs_poly.transform(to_canvas_crs) self.rubber.addGeometry(qgs_poly, None) self.rubber.show() self.iface.statusBarIface().showMessage(msg, 4000) def canvasReleaseEvent(self, event): '''Capture the coordinate when the mouse button has been released, format it, and copy it to the clipboard. pt is QgsPointXY''' pt = event.mapPoint() tz_name, msg = self.formatMessage(pt) if msg: clipboard = QApplication.clipboard() clipboard.setText(msg) self.iface.messageBar().pushMessage( "", "'{}' copied to the clipboard".format(msg), level=Qgis.Info, duration=2)
class GeodesicMeasureDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent): super(GeodesicMeasureDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.canvas = iface.mapCanvas() qset = QSettings() self.restoreGeometry( qset.value("ShapeTools/MeasureDialogGeometry", QByteArray(), type=QByteArray)) self.closeButton.clicked.connect(self.closeDialog) self.newButton.clicked.connect(self.newDialog) self.saveToLayerButton.clicked.connect(self.saveToLayer) self.saveToLayerButton.setEnabled(False) self.unitsComboBox.addItems(DISTANCE_LABELS) self.tableWidget.setColumnCount(3) self.tableWidget.setSortingEnabled(False) self.tableWidget.setHorizontalHeaderLabels( [tr('Heading To'), tr('Heading From'), tr('Distance')]) self.unitsComboBox.activated.connect(self.unitsChanged) self.capturedPoints = [] self.distances = [] self.activeMeasuring = True self.lastMotionPt = None self.unitsChanged() self.currentDistance = 0.0 self.pointRb = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry) self.pointRb.setColor(settings.rubberBandColor) self.pointRb.setIconSize(10) self.lineRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.lineRb.setColor(settings.rubberBandColor) self.lineRb.setWidth(3) self.tempRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.tempRb.setColor(settings.rubberBandColor) self.tempRb.setWidth(3) def ready(self): return self.activeMeasuring def stop(self): self.activeMeasuring = False self.lastMotionPt = None def closeEvent(self, event): self.closeDialog() def closeDialog(self): self.clear() QSettings().setValue("ShapeTools/MeasureDialogGeometry", self.saveGeometry()) self.close() def newDialog(self): self.clear() self.initGeodLabel() def initGeodLabel(self): label = tr('Ellipsoid: ') + settings.ellipseDescription self.geodLabel.setText(label) def keyPressed(self, key): index = len(self.capturedPoints) if index <= 0: return if self.motionReady(): if self.lastMotionPt == None: return (distance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index - 1], self.lastMotionPt) else: if index < 2: return (distance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index - 2], self.capturedPoints[index - 1]) distance = self.unitDistance(distance) clipboard = QApplication.clipboard() if key == Qt.Key_1 or key == Qt.Key_F: s = '{:.{prec}f}'.format(startAngle, prec=settings.measureSignificantDigits) clipboard.setText(s) self.iface.messageBar().pushMessage( "", "Heading to {} copied to the clipboard".format(s), level=Qgis.Info, duration=3) elif key == Qt.Key_2 or key == Qt.Key_T: s = '{:.{prec}f}'.format(endAngle, prec=settings.measureSignificantDigits) clipboard.setText(s) self.iface.messageBar().pushMessage( "", "Heading from {} copied to the clipboard".format(s), level=Qgis.Info, duration=3) elif key == Qt.Key_3 or key == Qt.Key_D: s = '{:.{prec}f}'.format(distance, prec=settings.measureSignificantDigits) clipboard.setText(s) self.iface.messageBar().pushMessage( "", "Distance {} copied to the clipboard".format(s), level=Qgis.Info, duration=3) elif key == Qt.Key_4 or key == Qt.Key_A: total = 0.0 num = len(self.capturedPoints) for i in range(1, num): (d, startA, endA) = self.calcParameters(self.capturedPoints[i - 1], self.capturedPoints[i]) total += d total = self.unitDistance(total) # Add in the motion distance if self.motionReady(): total += distance s = '{:.{prec}f}'.format(total, prec=settings.measureSignificantDigits) clipboard.setText(s) self.iface.messageBar().pushMessage( "", "Total distance {} copied to the clipboard".format(s), level=Qgis.Info, duration=3) else: return def unitsChanged(self): label = "Distance [{}]".format( DISTANCE_LABELS[self.unitsComboBox.currentIndex()]) item = QTableWidgetItem(label) self.tableWidget.setHorizontalHeaderItem(2, item) ptcnt = len(self.capturedPoints) if ptcnt >= 2: i = 0 while i < ptcnt - 1: item = QTableWidgetItem('{:.4f}'.format( self.unitDistance(self.distances[i]))) self.tableWidget.setItem(i, 2, item) i += 1 self.formatTotal() def motionReady(self): if len(self.capturedPoints) > 0 and self.activeMeasuring: return True return False def addPoint(self, pt, button): self.currentDistance = 0 index = len(self.capturedPoints) if index > 0 and pt == self.capturedPoints[index - 1]: # the clicked point is the same as the previous so just ignore it return self.capturedPoints.append(pt) # Add rubber band points canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(epsg4326, canvasCrs, QgsProject.instance()) ptCanvas = transform.transform(pt.x(), pt.y()) self.pointRb.addPoint(ptCanvas, True) # If there is more than 1 point add it to the table if index > 0: self.saveToLayerButton.setEnabled(True) (distance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index - 1], self.capturedPoints[index]) self.distances.append(distance) self.insertParams(index, distance, startAngle, endAngle) # Add Rubber Band Line linePts = self.getLinePts(distance, self.capturedPoints[index - 1], self.capturedPoints[index]) self.lineRb.addGeometry(QgsGeometry.fromPolylineXY(linePts), None) self.formatTotal() def inMotion(self, pt): index = len(self.capturedPoints) if index <= 0: return (self.currentDistance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index - 1], pt) self.insertParams(index, self.currentDistance, startAngle, endAngle) self.formatTotal() linePts = self.getLinePts(self.currentDistance, self.capturedPoints[index - 1], pt) self.lastMotionPt = pt self.tempRb.setToGeometry(QgsGeometry.fromPolylineXY(linePts), None) def calcParameters(self, pt1, pt2): l = geod.Inverse(pt1.y(), pt1.x(), pt2.y(), pt2.x()) az2 = (l['azi2'] + 180) % 360.0 if az2 > 180: az2 = az2 - 360.0 az1 = l['azi1'] # Check to see if the azimuth values should be in the range or 0 to 360 # The default is -180 to 180 if settings.mtAzMode: if az1 < 0: az1 += 360.0 if az2 < 0: az2 += 360 return (l['s12'], az1, az2) def getLinePts(self, distance, pt1, pt2): canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(epsg4326, canvasCrs, QgsProject.instance()) pt1c = transform.transform(pt1.x(), pt1.y()) pt2c = transform.transform(pt2.x(), pt2.y()) if distance < 10000: return [pt1c, pt2c] l = geod.InverseLine(pt1.y(), pt1.x(), pt2.y(), pt2.x()) n = int(math.ceil(distance / 10000.0)) if n > 20: n = 20 seglen = distance / n pts = [pt1c] for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) ptc = transform.transform(g['lon2'], g['lat2']) pts.append(ptc) pts.append(pt2c) return pts def saveToLayer(self): units = self.unitDesignator() canvasCrs = self.canvas.mapSettings().destinationCrs() fields = QgsFields() fields.append(QgsField("label", QVariant.String)) fields.append(QgsField("value", QVariant.Double)) fields.append(QgsField("units", QVariant.String)) fields.append(QgsField("heading_to", QVariant.Double)) fields.append(QgsField("heading_from", QVariant.Double)) fields.append(QgsField("total_dist", QVariant.Double)) layer = QgsVectorLayer("LineString?crs={}".format(canvasCrs.authid()), "Measurements", "memory") dp = layer.dataProvider() dp.addAttributes(fields) layer.updateFields() num = len(self.capturedPoints) total = 0.0 for i in range(1, num): (distance, startA, endA) = self.calcParameters(self.capturedPoints[i - 1], self.capturedPoints[i]) total += distance total = self.unitDistance(total) for i in range(1, num): (distance, startA, endA) = self.calcParameters(self.capturedPoints[i - 1], self.capturedPoints[i]) pts = self.getLinePts(distance, self.capturedPoints[i - 1], self.capturedPoints[i]) distance = self.unitDistance(distance) feat = QgsFeature(layer.fields()) feat.setAttribute(0, "{:.2f} {}".format(distance, units)) feat.setAttribute(1, distance) feat.setAttribute(2, units) feat.setAttribute(3, startA) feat.setAttribute(4, endA) feat.setAttribute(5, total) feat.setGeometry(QgsGeometry.fromPolylineXY(pts)) dp.addFeatures([feat]) label = QgsPalLayerSettings() label.fieldName = 'label' try: label.placement = QgsPalLayerSettings.Line except: label.placement = QgsPalLayerSettings.AboveLine format = label.format() format.setColor(settings.measureTextColor) format.setNamedStyle('Bold') label.setFormat(format) labeling = QgsVectorLayerSimpleLabeling(label) layer.setLabeling(labeling) layer.setLabelsEnabled(True) renderer = layer.renderer() renderer.symbol().setColor(settings.measureLineColor) renderer.symbol().setWidth(0.5) layer.updateExtents() QgsProject.instance().addMapLayer(layer) def insertParams(self, position, distance, startAngle, endAngle): if position > self.tableWidget.rowCount(): self.tableWidget.insertRow(position - 1) item = QTableWidgetItem('{:.4f}'.format(self.unitDistance(distance))) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tableWidget.setItem(position - 1, 2, item) item = QTableWidgetItem('{:.4f}'.format(startAngle)) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tableWidget.setItem(position - 1, 0, item) item = QTableWidgetItem('{:.4f}'.format(endAngle)) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tableWidget.setItem(position - 1, 1, item) def formatTotal(self): total = self.currentDistance ptcnt = len(self.capturedPoints) if ptcnt >= 2: i = 0 while i < ptcnt - 1: total += self.distances[i] i += 1 self.distanceLineEdit.setText('{:.2f}'.format( self.unitDistance(total))) def updateRBColor(self): self.pointRb.setColor(settings.rubberBandColor) self.lineRb.setColor(settings.rubberBandColor) self.tempRb.setColor(settings.rubberBandColor) def clear(self): self.tableWidget.setRowCount(0) self.capturedPoints = [] self.distances = [] self.activeMeasuring = True self.currentDistance = 0.0 self.distanceLineEdit.setText('') self.pointRb.reset(QgsWkbTypes.PointGeometry) self.lineRb.reset(QgsWkbTypes.LineGeometry) self.tempRb.reset(QgsWkbTypes.LineGeometry) self.saveToLayerButton.setEnabled(False) self.updateRBColor() def unitDistance(self, distance): units = self.unitsComboBox.currentIndex() if units == 0: # kilometers return distance / 1000.0 elif units == 1: # meters return distance elif units == 2: # centimeters return distance * QgsUnitTypes.fromUnitToUnitFactor( QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceCentimeters) elif units == 3: # miles return distance * QgsUnitTypes.fromUnitToUnitFactor( QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceMiles) elif units == 4: # yards return distance * QgsUnitTypes.fromUnitToUnitFactor( QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceYards) elif units == 5: # feet return distance * QgsUnitTypes.fromUnitToUnitFactor( QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceFeet) elif units == 6: # inches return distance * QgsUnitTypes.fromUnitToUnitFactor( QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceFeet) * 12 elif units == 7: # nautical miles return distance * QgsUnitTypes.fromUnitToUnitFactor( QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceNauticalMiles) def unitDesignator(self): units = self.unitsComboBox.currentIndex() return unitsAbbr[units]
class Qgis2threejsDialog(QDialog): STYLE_MAX_COUNT = 3 def __init__(self, iface): QDialog.__init__(self, iface.mainWindow()) self.iface = iface self.apiChanged22 = False # not QgsApplication.prefixPath().startswith("C:/OSGeo4W") # QGis.QGIS_VERSION_INT >= 20200 # Set up the user interface from Designer. self.ui = ui = Ui_Qgis2threejsDialog() ui.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint) ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]") ui.pushButton_Run.clicked.connect(self.run) ui.pushButton_Close.clicked.connect(self.reject) # DEM tab ui.toolButton_switchFocusMode.setVisible(False) ui.toolButton_PointTool.setVisible(False) ui.progressBar.setVisible(False) self.switchFocusMode(True) ui.toolButton_Browse.clicked.connect(self.browseClicked) ui.radioButton_Simple.toggled.connect(self.samplingModeChanged) ui.horizontalSlider_Resolution.valueChanged.connect(self.calculateResolution) ui.spinBox_Height.valueChanged.connect(self.updateQuads) ui.toolButton_switchFocusMode.clicked.connect(self.switchFocusModeClicked) ui.toolButton_PointTool.clicked.connect(self.startPointSelection) # Vector tab ui.treeWidget_VectorLayers.setHeaderLabel("Vector layers") self.initVectorStyleWidgets() ui.treeWidget_VectorLayers.currentItemChanged.connect(self.currentVectorLayerChanged) ui.treeWidget_VectorLayers.itemChanged.connect(self.vectorLayerItemChanged) ui.comboBox_ObjectType.currentIndexChanged.connect(self.objectTypeSelectionChanged) self.bar = None self.localBrowsingMode = True self.rb_quads = self.rb_point = None self.currentVectorLayer = None self.vectorPropertiesDict = {} self.objectTypeManager = ObjectTypeManager() # set map tool self.previousMapTool = None self.mapTool = RectangleMapTool(iface.mapCanvas()) self.connect(self.mapTool, SIGNAL("rectangleCreated()"), self.rectangleSelected) # self.mapTool = PointMapTool(iface.mapCanvas()) # QObject.connect(self.mapTool, SIGNAL("pointSelected()"), self.pointSelected) iface.mapCanvas().mapToolSet.connect(self.mapToolSet) self.startPointSelection() def exec_(self): ui = self.ui messages = [] # show message if crs unit is degrees mapSettings = self.iface.mapCanvas().mapSettings() if self.apiChanged22 else self.iface.mapCanvas().mapRenderer() if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]: self.showMessageBar("The unit of current CRS is degrees", "Terrain may not appear well.") # show message if there are no dem layer no_demlayer = ui.comboBox_DEMLayer.count() == 0 ui.pushButton_Run.setEnabled(not no_demlayer) if no_demlayer: self.showMessageBar("No DEM layer", "Load 1-band raster layer with GDAL provider.", QgsMessageBar.WARNING) return QDialog.exec_(self) def showMessageBar(self, title, text, level=QgsMessageBar.INFO): if self.bar is None: self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) ui = self.ui margins = ui.gridLayout.getContentsMargins() vl = ui.gridLayout.takeAt(0) ui.gridLayout.setContentsMargins(0,0,0,0) ui.gridLayout.addWidget(self.bar, 0, 0) ui.gridLayout.addItem(vl, 1, 0) ui.verticalLayout.setContentsMargins(margins[0], margins[1] / 2, margins[2], margins[3]) self.bar.pushMessage(title, text, level=level) def initDEMLayerList(self, layerId=None): # list 1 band raster layers self.ui.comboBox_DEMLayer.clear() for id, layer in QgsMapLayerRegistry().instance().mapLayers().items(): if layer.type() == QgsMapLayer.RasterLayer and layer.providerType() == "gdal" and layer.bandCount() == 1: self.ui.comboBox_DEMLayer.addItem(layer.name(), id) # select the last selected layer if layerId is not None: index = self.ui.comboBox_DEMLayer.findData(layerId) if index != -1: self.ui.comboBox_DEMLayer.setCurrentIndex(index) return index return -1 def initVectorLayerTree(self, vectorPropertiesDict): self.vectorPropertiesDict = vectorPropertiesDict tree = self.ui.treeWidget_VectorLayers tree.clear() # add vector layers into tree widget self.treeTopItems = topItems = {QGis.Point:QTreeWidgetItem(tree, ["Point"]), QGis.Line:QTreeWidgetItem(tree, ["Line"]), QGis.Polygon:QTreeWidgetItem(tree, ["Polygon"])} self.vlItems = {} for layer in self.iface.legendInterface().layers(): if layer.type() != QgsMapLayer.VectorLayer: continue geometry_type = layer.geometryType() if geometry_type in [QGis.Point, QGis.Line, QGis.Polygon]: self.vlItems[layer.id()] = item = QTreeWidgetItem(topItems[geometry_type], [layer.name()]) if layer.id() in self.vectorPropertiesDict: isVisible = self.vectorPropertiesDict[layer.id()]["visible"] else: isVisible = False #self.iface.legendInterface().isLayerVisible(layer) check_state = Qt.Checked if isVisible else Qt.Unchecked item.setData(0, Qt.CheckStateRole, check_state) item.setData(0, Qt.UserRole, layer.id()) #item.setDisabled(True) #item.setData(0, Qt.CheckStateRole, Qt.Unchecked) for item in topItems.values(): tree.expandItem(item) self.setVectorStylesEnabled(False) def initVectorStyleWidgets(self): self.colorWidget = StyleWidget(StyleWidget.COLOR) self.ui.verticalLayout_Styles.addWidget(self.colorWidget) self.heightWidget = StyleWidget(StyleWidget.HEIGHT) self.ui.verticalLayout_zCoordinate.addWidget(self.heightWidget) self.styleWidgets = [] for i in range(self.STYLE_MAX_COUNT): widget = StyleWidget() widget.setVisible(False) self.ui.verticalLayout_Styles.addWidget(widget) self.styleWidgets.append(widget) def currentVectorLayerChanged(self, currentItem, previousItem): # save properties of previous item if previousItem is not None: layerid = previousItem.data(0, Qt.UserRole) if layerid is not None: self.saveVectorProperties(layerid) layerid = currentItem.data(0, Qt.UserRole) if layerid is None: self.currentVectorLayer = None return self.currentVectorLayer = layer = QgsMapLayerRegistry().instance().mapLayer(layerid) if layer is None: return for i in range(self.STYLE_MAX_COUNT): self.styleWidgets[i].hide() obj_types = self.objectTypeManager.objectTypeNames(layer.geometryType()) ui = self.ui ui.comboBox_ObjectType.blockSignals(True) ui.comboBox_ObjectType.clear() ui.comboBox_ObjectType.addItems(obj_types) ui.comboBox_ObjectType.blockSignals(False) # set up property widgets self.objectTypeSelectionChanged() if layerid in self.vectorPropertiesDict: # restore properties self.restoreVectorProperties(layerid) self.setVectorStylesEnabled(currentItem.data(0, Qt.CheckStateRole) == Qt.Checked) def vectorLayerItemChanged(self, item, column): # update style form enablement currentItem = self.ui.treeWidget_VectorLayers.currentItem() if currentItem: self.setVectorStylesEnabled(currentItem.data(0, Qt.CheckStateRole) == Qt.Checked) def objectTypeSelectionChanged(self, idx=None): layer = self.currentVectorLayer try: ve = float(ui.lineEdit_zFactor.text()) except: ve = 1 mapTo3d = MapTo3D(self.iface.mapCanvas(), verticalExaggeration=ve) self.objectTypeManager.setupForm(self, mapTo3d, layer, layer.geometryType(), self.ui.comboBox_ObjectType.currentIndex()) def numericFields(self, layer): # get attributes of a sample feature and create numeric field name list numeric_fields = [] f = QgsFeature() layer.getFeatures().nextFeature(f) for field in f.fields(): isNumeric = False try: float(f.attribute(field.name())) isNumeric = True except: pass if isNumeric: numeric_fields.append(field.name()) return numeric_fields def setVectorStylesEnabled(self, enabled): self.ui.comboBox_ObjectType.setEnabled(enabled) self.ui.label_ObjectType.setEnabled(enabled) self.colorWidget.setEnabled(enabled) self.heightWidget.setEnabled(enabled) for i in range(self.STYLE_MAX_COUNT): self.styleWidgets[i].setEnabled(enabled) def saveVectorProperties(self, layerid): properties = {} layer = QgsMapLayerRegistry().instance().mapLayer(layerid) itemIndex = self.ui.comboBox_ObjectType.currentIndex() properties["itemindex"] = itemIndex properties["typeitem"] = self.objectTypeManager.objectTypeItem(layer.geometryType(), itemIndex) properties["visible"] = self.vlItems[self.currentVectorLayer.id()].data(0, Qt.CheckStateRole) == Qt.Checked properties["color"] = self.colorWidget.values() properties["height"] = self.heightWidget.values() for i in range(self.STYLE_MAX_COUNT): if self.styleWidgets[i].isVisible(): properties[i] = self.styleWidgets[i].values() self.vectorPropertiesDict[layerid] = properties def restoreVectorProperties(self, layerid): properties = self.vectorPropertiesDict[layerid] self.ui.comboBox_ObjectType.setCurrentIndex(properties["itemindex"]) self.colorWidget.setValues(properties["color"]) self.heightWidget.setValues(properties["height"]) for i in range(self.STYLE_MAX_COUNT): if i in properties: self.styleWidgets[i].setValues(properties[i]) def calculateResolution(self, v=None): extent = self.iface.mapCanvas().extent() renderer = self.iface.mapCanvas().mapRenderer() size = 100 * self.ui.horizontalSlider_Resolution.value() self.ui.label_Resolution.setText("about {0} x {0} px".format(size)) # calculate resolution and size width, height = renderer.width(), renderer.height() s = (size * size / float(width * height)) ** 0.5 if s < 1: width = int(width * s) height = int(height * s) xres = extent.width() / width yres = extent.height() / height self.ui.lineEdit_HRes.setText(str(xres)) self.ui.lineEdit_VRes.setText(str(yres)) self.ui.lineEdit_Width.setText(str(width + 1)) self.ui.lineEdit_Height.setText(str(height + 1)) def progress(self, percentage): self.ui.progressBar.setValue(percentage) self.ui.progressBar.setVisible(percentage != 100) def run(self): ui = self.ui filename = ui.lineEdit_OutputFilename.text() # ""=Temporary file if filename != "" and QFileInfo(filename).exists() and QMessageBox.question(None, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok: return self.endPointSelection() item = ui.treeWidget_VectorLayers.currentItem() if item: self.saveVectorProperties(item.data(0, Qt.UserRole)) ui.pushButton_Run.setEnabled(False) self.progress(0) canvas = self.iface.mapCanvas() htmlfilename = ui.lineEdit_OutputFilename.text() demlayerid = ui.comboBox_DEMLayer.itemData(ui.comboBox_DEMLayer.currentIndex()) mapTo3d = MapTo3D(canvas, verticalExaggeration=float(ui.lineEdit_zFactor.text())) if self.ui.radioButton_Simple.isChecked(): dem_width = int(ui.lineEdit_Width.text()) dem_height = int(ui.lineEdit_Height.text()) context = OutputContext(mapTo3d, canvas, demlayerid, self.vectorPropertiesDict, self.objectTypeManager, self.localBrowsingMode, dem_width, dem_height) htmlfilename = runSimple(htmlfilename, context, self.progress) else: context = OutputContext(mapTo3d, canvas, demlayerid, self.vectorPropertiesDict, self.objectTypeManager, self.localBrowsingMode) htmlfilename = runAdvanced(htmlfilename, context, self, self.progress) self.progress(100) ui.pushButton_Run.setEnabled(True) if htmlfilename is None: return self.clearRubberBands() if not tools.openHTMLFile(htmlfilename): return QDialog.accept(self) def reject(self): self.endPointSelection() self.clearRubberBands() QDialog.reject(self) def startPointSelection(self): canvas = self.iface.mapCanvas() self.previousMapTool = canvas.mapTool() canvas.setMapTool(self.mapTool) self.ui.toolButton_PointTool.setVisible(False) def endPointSelection(self): self.mapTool.reset() self.iface.mapCanvas().setMapTool(self.previousMapTool) def rectangleSelected(self): ui = self.ui ui.radioButton_Advanced.setChecked(True) rect = self.mapTool.rectangle() toRect = rect.width() and rect.height() self.switchFocusMode(toRect) ui.lineEdit_xmin.setText(str(rect.xMinimum())) ui.lineEdit_ymin.setText(str(rect.yMinimum())) ui.lineEdit_xmax.setText(str(rect.xMaximum())) ui.lineEdit_ymax.setText(str(rect.yMaximum())) quadtree = QuadTree(self.iface.mapCanvas().extent()) quadtree.buildTreeByRect(rect, self.ui.spinBox_Height.value()) self.createRubberBands(quadtree.quads(), rect.center()) self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) def pointSelected(self): # set values of controls self.ui.lineEdit_CenterX.setText(str(self.mapTool.point.x())) self.ui.lineEdit_CenterY.setText(str(self.mapTool.point.y())) self.ui.radioButton_Advanced.setChecked(True) quadtree = QuadTree(self.iface.mapCanvas().extent(), self.mapTool.point, self.ui.spinBox_Height.value()) self.createRubberBands(quadtree.quads(), self.mapTool.point) self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) def mapToolSet(self, mapTool): if mapTool != self.mapTool: self.ui.toolButton_PointTool.setVisible(True) def createQuadTree(self): ui = self.ui try: c = map(float, [ui.lineEdit_xmin.text(), ui.lineEdit_ymin.text(), ui.lineEdit_xmax.text(), ui.lineEdit_ymax.text()]) except: return None quadtree = QuadTree(self.iface.mapCanvas().extent()) quadtree.buildTreeByRect(QgsRectangle(c[0], c[1], c[2], c[3]), ui.spinBox_Height.value()) return quadtree def createRubberBands(self, quads, point=None): self.clearRubberBands() # create quads with rubber band self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line) self.rb_quads.setColor(Qt.blue) self.rb_quads.setWidth(1) for quad in quads: points = [] extent = quad.extent points.append(QgsPoint(extent.xMinimum(), extent.yMinimum())) points.append(QgsPoint(extent.xMinimum(), extent.yMaximum())) points.append(QgsPoint(extent.xMaximum(), extent.yMaximum())) points.append(QgsPoint(extent.xMaximum(), extent.yMinimum())) self.rb_quads.addGeometry(QgsGeometry.fromPolygon([points]), None) self.log(extent.toString()) self.log("Quad count: %d" % len(quads)) # create a point with rubber band if point: self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rb_point.setColor(Qt.red) self.rb_point.addPoint(point) def clearRubberBands(self): # clear quads and point if self.rb_quads: self.iface.mapCanvas().scene().removeItem(self.rb_quads) self.rb_quads = None if self.rb_point: self.iface.mapCanvas().scene().removeItem(self.rb_point) self.rb_point = None def browseClicked(self): directory = self.ui.lineEdit_OutputFilename.text() if directory == "": directory = QDir.homePath() filename = QFileDialog.getSaveFileName(self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite) if filename != "": self.ui.lineEdit_OutputFilename.setText(filename) def samplingModeChanged(self): ui = self.ui isSimpleMode = ui.radioButton_Simple.isChecked() simple_widgets = [ui.horizontalSlider_Resolution, ui.lineEdit_Width, ui.lineEdit_Height, ui.lineEdit_HRes, ui.lineEdit_VRes] for w in simple_widgets: w.setEnabled(isSimpleMode) isAdvancedMode = not isSimpleMode advanced_widgets = [ui.spinBox_Height, ui.lineEdit_xmin, ui.lineEdit_ymin, ui.lineEdit_xmax, ui.lineEdit_ymax, ui.toolButton_switchFocusMode] for w in advanced_widgets: w.setEnabled(isAdvancedMode) def updateQuads(self, v=None): quadtree = self.createQuadTree() if quadtree: self.createRubberBands(quadtree.quads(), quadtree.focusRect.center()) else: self.clearRubberBands() def switchFocusModeClicked(self): self.switchFocusMode(not self.ui.label_xmin.isVisible()) def switchFocusMode(self, toRect): ui = self.ui toPoint = not toRect ui.label_xmin.setVisible(toRect) ui.label_ymin.setVisible(toRect) ui.lineEdit_xmin.setVisible(toRect) ui.lineEdit_ymin.setVisible(toRect) suffix = "max" if toRect else "" ui.label_xmax.setText("x" + suffix) ui.label_ymax.setText("y" + suffix) mode = "point" if toRect else "rectangle" ui.toolButton_switchFocusMode.setText("To " + mode + " selection") selection = "area" if toRect else "point" action = "Stroke a rectangle" if toRect else "Click" ui.label_Focus.setText("Focus {0} ({1} on map canvas to set values)".format(selection, action)) def log(self, msg): if debug_mode: qDebug(msg)
class LatLonTools: digitizerDialog = None convertCoordinateDialog = None def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.crossRb.setColor(Qt.red) self.provider = LatLonToolsProvider() self.toolbar = self.iface.addToolBar('Lat Lon Tools Toolbar') self.toolbar.setObjectName('LatLonToolsToolbar') def initGui(self): '''Initialize Lot Lon Tools GUI.''' # Initialize the Settings Dialog box self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow()) self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface) self.showMapTool = ShowOnMapTool(self.iface) # Add Interface for Coordinate Capturing icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png") self.copyAction = QAction(icon, "Copy/Display Coordinate", self.iface.mainWindow()) self.copyAction.setObjectName('latLonToolsCopy') self.copyAction.triggered.connect(self.startCapture) self.copyAction.setCheckable(True) self.toolbar.addAction(self.copyAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction) # Add Interface for External Map icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png") self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow()) self.externMapAction.setObjectName('latLonToolsExternalMap') self.externMapAction.triggered.connect(self.setShowMapTool) self.externMapAction.setCheckable(True) self.toolbar.addAction(self.externMapAction) self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction) # Add Interface for Zoom to Coordinate icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png") self.zoomToAction = QAction(icon, "Zoom To Coordinate", self.iface.mainWindow()) self.zoomToAction.setObjectName('latLonToolsZoom') self.zoomToAction.triggered.connect(self.showZoomToDialog) self.toolbar.addAction(self.zoomToAction) self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction) self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow()) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog) self.zoomToDialog.hide() # Add Interface for Multi point zoom icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png') self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow()) self.multiZoomToAction.setObjectName('latLonToolsMultiZoom') self.multiZoomToAction.triggered.connect(self.multiZoomTo) self.toolbar.addAction(self.multiZoomToAction) self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction) self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow()) self.multiZoomDialog.hide() self.multiZoomDialog.setFloating(True) # Create the coordinate converter menu icon = QIcon(':/images/themes/default/mIconProjectionEnabled.svg') self.convertCoordinatesAction = QAction(icon, "Coordinate Conversion", self.iface.mainWindow()) self.convertCoordinatesAction.setObjectName('latLonToolsCoordinateConversion') self.convertCoordinatesAction.triggered.connect(self.convertCoordinatesTool) self.toolbar.addAction(self.convertCoordinatesAction) self.iface.addPluginToMenu("Lat Lon Tools", self.convertCoordinatesAction) # Create the conversions menu menu = QMenu() icon = QIcon(os.path.dirname(__file__) + '/images/field2geom.png') action = menu.addAction(icon, "Fields to point layer", self.field2geom) action.setObjectName('latLonToolsField2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.png') action = menu.addAction(icon, "Point layer to fields", self.geom2Field) action.setObjectName('latLonToolsGeom2Field') icon = QIcon(os.path.dirname(__file__) + '/images/pluscodes.png') action = menu.addAction(icon, "Plus Codes to point layer", self.PlusCodestoLayer) action.setObjectName('latLonToolsPlusCodes2Geom') action = menu.addAction(icon, "Point layer to Plus Codes", self.toPlusCodes) action.setObjectName('latLonToolsGeom2PlusCodes') icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png') action = menu.addAction(icon, "MGRS to point layer", self.MGRStoLayer) action.setObjectName('latLonToolsMGRS2Geom') icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png') action = menu.addAction(icon, "Point layer to MGRS", self.toMGRS) action.setObjectName('latLonToolsGeom2MGRS') self.conversionsAction = QAction(icon, "Conversions", self.iface.mainWindow()) self.conversionsAction.setMenu(menu) self.iface.addPluginToMenu('Lat Lon Tools', self.conversionsAction) # Add to Digitize Toolbar icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.png') self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow()) self.digitizeAction.setObjectName('latLonToolsDigitize') self.digitizeAction.triggered.connect(self.digitizeClicked) self.digitizeAction.setEnabled(False) self.toolbar.addAction(self.digitizeAction) self.iface.addPluginToMenu('Lat Lon Tools', self.digitizeAction) # Add Interface for copying the canvas extent icon = QIcon(os.path.dirname(__file__) + "/images/copycanvas.png") self.copyCanvasAction = QAction(icon, "Copy Canvas Bounding Box", self.iface.mainWindow()) self.copyCanvasAction.setObjectName('latLonToolsCopyCanvas') self.copyCanvasAction.triggered.connect(self.copyCanvas) self.toolbar.addAction(self.copyCanvasAction) self.iface.addPluginToMenu("Lat Lon Tools", self.copyCanvasAction) # Initialize the Settings Dialog Box settingsicon = QIcon(os.path.dirname(__file__) + '/images/settings.png') self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow()) self.settingsAction.setObjectName('latLonToolsSettings') self.settingsAction.triggered.connect(self.settings) self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction) # Help icon = QIcon(os.path.dirname(__file__) + '/images/help.png') self.helpAction = QAction(icon, "Help", self.iface.mainWindow()) self.helpAction.setObjectName('latLonToolsHelp') self.helpAction.triggered.connect(self.help) self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction) self.iface.currentLayerChanged.connect(self.currentLayerChanged) self.canvas.mapToolSet.connect(self.unsetTool) self.enableDigitizeTool() # Add the processing provider QgsApplication.processingRegistry().addProvider(self.provider) def unsetTool(self, tool): '''Uncheck the Copy Lat Lon tool''' try: if not isinstance(tool, CopyLatLonTool): self.copyAction.setChecked(False) self.multiZoomDialog.stopCapture() self.mapTool.capture4326 = False if not isinstance(tool, ShowOnMapTool): self.externMapAction.setChecked(False) except Exception: pass def unload(self): '''Unload LatLonTools from the QGIS interface''' self.zoomToDialog.removeMarker() self.multiZoomDialog.removeMarkers() self.canvas.unsetMapTool(self.mapTool) self.canvas.unsetMapTool(self.showMapTool) self.iface.removePluginMenu('Lat Lon Tools', self.copyAction) self.iface.removePluginMenu('Lat Lon Tools', self.copyCanvasAction) self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction) self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction) self.iface.removePluginMenu('Lat Lon Tools', self.convertCoordinatesAction) self.iface.removePluginMenu('Lat Lon Tools', self.conversionsAction) self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction) self.iface.removePluginMenu('Lat Lon Tools', self.helpAction) self.iface.removePluginMenu('Lat Lon Tools', self.digitizeAction) self.iface.removeDockWidget(self.zoomToDialog) self.iface.removeDockWidget(self.multiZoomDialog) # Remove Toolbar Icons self.iface.removeToolBarIcon(self.copyAction) self.iface.removeToolBarIcon(self.copyCanvasAction) self.iface.removeToolBarIcon(self.zoomToAction) self.iface.removeToolBarIcon(self.externMapAction) self.iface.removeToolBarIcon(self.multiZoomToAction) self.iface.removeToolBarIcon(self.convertCoordinatesAction) self.iface.removeToolBarIcon(self.digitizeAction) del self.toolbar self.zoomToDialog = None self.multiZoomDialog = None self.settingsDialog = None self.showMapTool = None self.mapTool = None self.digitizerDialog = None self.convertCoordinateDialog = None QgsApplication.processingRegistry().removeProvider(self.provider) def startCapture(self): '''Set the focus of the copy coordinate tool and check it''' self.copyAction.setChecked(True) self.canvas.setMapTool(self.mapTool) def copyCanvas(self): extent = self.iface.mapCanvas().extent() canvasCrs = self.canvas.mapSettings().destinationCrs() if settings.bBoxCrs == 0 and canvasCrs != epsg4326: transform = QgsCoordinateTransform(canvasCrs, epsg4326, QgsProject.instance()) p1x, p1y = transform.transform(float(extent.xMinimum()), float(extent.yMinimum())) p2x, p2y = transform.transform(float(extent.xMaximum()), float(extent.yMaximum())) extent.set(p1x, p1y, p2x, p2y) delim = settings.bBoxDelimiter prefix = settings.bBoxPrefix suffix = settings.bBoxSuffix precision = settings.bBoxDigits outStr = '' minX = extent.xMinimum() minY = extent.yMinimum() maxX = extent.xMaximum() maxY = extent.yMaximum() if settings.bBoxFormat == 0: # minX,minY,maxX,maxY - using the delimiter outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format( minX, delim, minY, delim, maxX, delim, maxY, prec=precision) elif settings.bBoxFormat == 1: # minX,maxX,minY,maxY - Using the selected delimiter' outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format( minX, delim, maxX, delim, minY, delim, maxY, prec=precision) elif settings.bBoxFormat == 2: # x1 y1,x2 y2,x3 y3,x4 y4,x1 y1 - Polygon format outStr = '{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f}'.format( minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision) elif settings.bBoxFormat == 3: # x1,y1 x2,y2 x3,y3 x4,y4 x1,y1 - Polygon format outStr = '{:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f}'.format( minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision) elif settings.bBoxFormat == 4: # WKT Polygon outStr = extent.asWktPolygon() elif settings.bBoxFormat == 5: # bbox: [minX, minY, maxX, maxY] - MapProxy outStr = 'bbox: [{}, {}, {}, {}]'.format( minX, minY, maxX, maxY) elif settings.bBoxFormat == 6: # bbox: [minX, minY, maxX, maxY] - MapProxy outStr = 'bbox={},{},{},{}'.format( minX, minY, maxX, maxY) outStr = '{}{}{}'.format(prefix, outStr, suffix) clipboard = QApplication.clipboard() clipboard.setText(outStr) self.iface.messageBar().pushMessage("", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4) def setShowMapTool(self): '''Set the focus of the external map tool and check it''' self.externMapAction.setChecked(True) self.canvas.setMapTool(self.showMapTool) def showZoomToDialog(self): '''Show the zoom to docked widget.''' self.zoomToDialog.show() def convertCoordinatesTool(self): '''Display the Convert Coordinate Tool Dialog box.''' if self.convertCoordinateDialog is None: from .coordinateConverter import CoordinateConverterWidget self.convertCoordinateDialog = CoordinateConverterWidget(self, self.settingsDialog, self.iface, self.iface.mainWindow()) self.convertCoordinateDialog.show() def multiZoomTo(self): '''Display the Multi-zoom to dialog box''' self.multiZoomDialog.show() def field2geom(self): '''Convert layer containing a point x & y coordinate to a new point layer''' processing.execAlgorithmDialog('latlontools:field2geom', {}) def geom2Field(self): '''Convert layer geometry to a text string''' processing.execAlgorithmDialog('latlontools:geom2field', {}) def toMGRS(self): '''Display the to MGRS dialog box''' processing.execAlgorithmDialog('latlontools:point2mgrs', {}) def MGRStoLayer(self): '''Display the to MGRS dialog box''' processing.execAlgorithmDialog('latlontools:mgrs2point', {}) def toPlusCodes(self): processing.execAlgorithmDialog('latlontools:point2pluscodes', {}) def PlusCodestoLayer(self): processing.execAlgorithmDialog('latlontools:pluscodes2point', {}) def settings(self): '''Show the settings dialog box''' self.settingsDialog.show() def help(self): '''Display a help page''' url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2) def settingsChanged(self): # Settings may have changed so we need to make sure the zoomToDialog window is configured properly self.zoomToDialog.configure() self.multiZoomDialog.settingsChanged() def zoomTo(self, srcCrs, lat, lon): canvasCrs = self.canvas.mapSettings().destinationCrs() transform = QgsCoordinateTransform(srcCrs, canvasCrs, QgsProject.instance()) x, y = transform.transform(float(lon), float(lat)) rect = QgsRectangle(x, y, x, y) self.canvas.setExtent(rect) pt = QgsPointXY(x, y) self.highlight(pt) self.canvas.refresh() return pt def highlight(self, point): currExt = self.canvas.extent() leftPt = QgsPoint(currExt.xMinimum(), point.y()) rightPt = QgsPoint(currExt.xMaximum(), point.y()) topPt = QgsPoint(point.x(), currExt.yMaximum()) bottomPt = QgsPoint(point.x(), currExt.yMinimum()) horizLine = QgsGeometry.fromPolyline([leftPt, rightPt]) vertLine = QgsGeometry.fromPolyline([topPt, bottomPt]) self.crossRb.reset(QgsWkbTypes.LineGeometry) self.crossRb.addGeometry(horizLine, None) self.crossRb.addGeometry(vertLine, None) QTimer.singleShot(700, self.resetRubberbands) def resetRubberbands(self): self.crossRb.reset() def digitizeClicked(self): if self.digitizerDialog is None: from .digitizer import DigitizerWidget self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow()) self.digitizerDialog.show() def currentLayerChanged(self): layer = self.iface.activeLayer() if layer is not None: try: layer.editingStarted.disconnect(self.layerEditingChanged) except Exception: pass try: layer.editingStopped.disconnect(self.layerEditingChanged) except Exception: pass if isinstance(layer, QgsVectorLayer): layer.editingStarted.connect(self.layerEditingChanged) layer.editingStopped.connect(self.layerEditingChanged) self.enableDigitizeTool() def layerEditingChanged(self): self.enableDigitizeTool() def enableDigitizeTool(self): self.digitizeAction.setEnabled(False) layer = self.iface.activeLayer() if layer is not None and isinstance(layer, QgsVectorLayer) and (layer.geometryType() == QgsWkbTypes.PointGeometry) and layer.isEditable(): self.digitizeAction.setEnabled(True) else: if self.digitizerDialog is not None: self.digitizerDialog.close()
class Qgis2threejsDialog(QDialog): def __init__(self, iface, objectTypeManager, pluginManager, exportSettings=None, lastTreeItemData=None): QDialog.__init__(self, iface.mainWindow()) self.iface = iface self.objectTypeManager = objectTypeManager self.pluginManager = pluginManager self._settings = exportSettings or {} self.lastTreeItemData = lastTreeItemData self.localBrowsingMode = True self.rb_quads = self.rb_point = None self.templateType = None self.currentItem = None self.currentPage = None # Set up the user interface from Designer. self.ui = ui = Ui_Qgis2threejsDialog() ui.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint) # output html filename ui.lineEdit_OutputFilename.setText( self._settings.get("OutputFilename", "")) ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]") # settings button icon = QIcon(os.path.join(tools.pluginDir(), "icons", "settings.png")) ui.toolButton_Settings.setIcon(icon) # popup menu displayed when settings button is pressed items = [["Load Settings...", self.loadSettings], ["Save Settings As...", self.saveSettings], [None, None], ["Clear Settings", self.clearSettings], [None, None], ["Plugin Settings...", self.pluginSettings]] self.menu = QMenu() self.menu_actions = [] for text, slot in items: if text: action = QAction(text, iface.mainWindow()) action.triggered.connect(slot) self.menu.addAction(action) self.menu_actions.append(action) else: self.menu.addSeparator() ui.toolButton_Settings.setMenu(self.menu) ui.toolButton_Settings.setPopupMode(QToolButton.InstantPopup) # progress bar and message label ui.progressBar.setVisible(False) ui.label_MessageIcon.setVisible(False) # buttons ui.pushButton_Run.clicked.connect(self.run) ui.pushButton_Close.clicked.connect(self.reject) ui.pushButton_Help.clicked.connect(self.help) # set up map tool self.previousMapTool = None self.mapTool = RectangleMapTool(iface.mapCanvas()) #self.mapTool = PointMapTool(iface.mapCanvas()) # set up the template combo box self.initTemplateList() self.ui.comboBox_Template.currentIndexChanged.connect( self.currentTemplateChanged) # set up the properties pages self.pages = {} self.pages[ppages.PAGE_WORLD] = ppages.WorldPropertyPage(self) self.pages[ppages.PAGE_CONTROLS] = ppages.ControlsPropertyPage(self) self.pages[ppages.PAGE_DEM] = ppages.DEMPropertyPage(self) self.pages[ppages.PAGE_VECTOR] = ppages.VectorPropertyPage(self) container = ui.propertyPagesContainer for page in self.pages.itervalues(): page.hide() container.addWidget(page) # build object tree self.topItemPages = { ObjectTreeItem.ITEM_WORLD: ppages.PAGE_WORLD, ObjectTreeItem.ITEM_CONTROLS: ppages.PAGE_CONTROLS, ObjectTreeItem.ITEM_DEM: ppages.PAGE_DEM } self.initObjectTree() self.ui.treeWidget.currentItemChanged.connect( self.currentObjectChanged) self.ui.treeWidget.itemChanged.connect(self.objectItemChanged) self.currentTemplateChanged() # update item visibility ui.toolButton_Browse.clicked.connect(self.browseClicked) #iface.mapCanvas().mapToolSet.connect(self.mapToolSet) # to show button to enable own map tool def settings(self, clean=False): # save settings of current panel item = self.ui.treeWidget.currentItem() if item and self.currentPage: self.saveProperties(item, self.currentPage) # plugin version self._settings["PluginVersion"] = plugin_version # template and output html file path self._settings["Template"] = self.ui.comboBox_Template.currentText() self._settings[ "OutputFilename"] = self.ui.lineEdit_OutputFilename.text() if not clean: return self._settings # clean up settings - remove layers that don't exist in the layer registry registry = QgsMapLayerRegistry.instance() for itemId in [ ObjectTreeItem.ITEM_OPTDEM, ObjectTreeItem.ITEM_POINT, ObjectTreeItem.ITEM_LINE, ObjectTreeItem.ITEM_POLYGON ]: parent = self._settings.get(itemId, {}) for layerId in parent.keys(): if registry.mapLayer(layerId) is None: del parent[layerId] return self._settings def setSettings(self, settings): self._settings = settings # template and output html file path templateName = settings.get("Template") if templateName: cbox = self.ui.comboBox_Template index = cbox.findText(templateName) if index != -1: cbox.setCurrentIndex(index) filename = settings.get("OutputFilename") if filename: self.ui.lineEdit_OutputFilename.setText(filename) # update object tree self.ui.treeWidget.blockSignals(True) self.initObjectTree() self.ui.treeWidget.blockSignals(False) # update tree item visibility self.templateType = None self.currentTemplateChanged() def loadSettings(self): # file open dialog directory = QgsProject.instance().homePath() if not directory: directory = os.path.split( self.ui.lineEdit_OutputFilename.text())[0] if not directory: directory = QDir.homePath() filterString = "Settings files (*.qto3settings);;All files (*.*)" filename = QFileDialog.getOpenFileName(self, "Load Export Settings", directory, filterString) if not filename: return # load settings from file (.qto3settings) import json with open(filename) as f: settings = json.load(f) self.setSettings(settings) def saveSettings(self, filename=None): if not filename: # file save dialog directory = QgsProject.instance().homePath() if not directory: directory = os.path.split( self.ui.lineEdit_OutputFilename.text())[0] if not directory: directory = QDir.homePath() filename = QFileDialog.getSaveFileName( self, "Save Export Settings", directory, "Settings files (*.qto3settings)") if not filename: return # append .qto3settings extension if filename doesn't have if os.path.splitext(filename)[1].lower() != ".qto3settings": filename += ".qto3settings" # save settings to file (.qto3settings) import codecs import json with codecs.open(filename, "w", "UTF-8") as f: json.dump(self.settings(True), f, ensure_ascii=False, indent=2, sort_keys=True) logMessage(u"Settings saved: {0}".format(filename)) def clearSettings(self): if QMessageBox.question(self, "Qgis2threejs", "Are you sure to clear all export settings?", QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok: self.setSettings({}) def pluginSettings(self): from settingsdialog import SettingsDialog dialog = SettingsDialog(self) if dialog.exec_(): self.pluginManager.reloadPlugins() self.pages[ppages.PAGE_DEM].initLayerComboBox() def showMessageBar(self, text, level=QgsMessageBar.INFO): # from src/gui/qgsmessagebaritem.cpp if level == QgsMessageBar.CRITICAL: msgIcon = "/mIconCritical.png" bgColor = "#d65253" elif level == QgsMessageBar.WARNING: msgIcon = "/mIconWarn.png" bgColor = "#ffc800" else: msgIcon = "/mIconInfo.png" bgColor = "#e7f5fe" stylesheet = "QLabel {{ background-color:{0}; }}".format(bgColor) label = self.ui.label_MessageIcon label.setPixmap(QgsApplication.getThemeIcon(msgIcon).pixmap(24)) label.setStyleSheet(stylesheet) label.setVisible(True) label = self.ui.label_Status label.setText(text) label.setStyleSheet(stylesheet) def clearMessageBar(self): self.ui.label_MessageIcon.setVisible(False) self.ui.label_Status.setText("") self.ui.label_Status.setStyleSheet( "QLabel { background-color: rgba(0, 0, 0, 0); }") def initTemplateList(self): cbox = self.ui.comboBox_Template cbox.clear() templateDir = QDir(tools.templateDir()) for i, entry in enumerate(templateDir.entryList(["*.html", "*.htm"])): cbox.addItem(entry) config = tools.getTemplateConfig(entry) # get template type templateType = config.get("type", "plain") cbox.setItemData(i, templateType, Qt.UserRole) # set tool tip text desc = config.get("description", "") if desc: cbox.setItemData(i, desc, Qt.ToolTipRole) # select the template of the settings templatePath = self._settings.get("Template") # if no template setting, select the last used template if not templatePath: templatePath = QSettings().value("/Qgis2threejs/lastTemplate", def_vals.template, type=unicode) if templatePath: index = cbox.findText(templatePath) if index != -1: cbox.setCurrentIndex(index) return index return -1 def initObjectTree(self): tree = self.ui.treeWidget tree.clear() # add vector and raster layers into tree widget topItems = {} for id, name in zip(ObjectTreeItem.topItemIds, ObjectTreeItem.topItemNames): item = QTreeWidgetItem(tree, [name]) item.setData(0, Qt.UserRole, id) topItems[id] = item optDEMChecked = False for layer in self.iface.legendInterface().layers(): parentId = ObjectTreeItem.parentIdByLayer(layer) if parentId is None: continue item = QTreeWidgetItem(topItems[parentId], [layer.name()]) isVisible = self._settings.get(parentId, {}).get( layer.id(), {}).get( "visible", False) #self.iface.legendInterface().isLayerVisible(layer) check_state = Qt.Checked if isVisible else Qt.Unchecked item.setData(0, Qt.CheckStateRole, check_state) item.setData(0, Qt.UserRole, layer.id()) if parentId == ObjectTreeItem.ITEM_OPTDEM and isVisible: optDEMChecked = True for id, item in topItems.iteritems(): if id != ObjectTreeItem.ITEM_OPTDEM or optDEMChecked: tree.expandItem(item) # disable additional DEM item which is selected as main DEM layerId = self._settings.get(ObjectTreeItem.ITEM_DEM, {}).get("comboBox_DEMLayer") if layerId: self.primaryDEMChanged(layerId) def saveProperties(self, item, page): properties = page.properties() parent = item.parent() if parent is None: # top level item self._settings[item.data(0, Qt.UserRole)] = properties else: # layer item parentId = parent.data(0, Qt.UserRole) if parentId not in self._settings: self._settings[parentId] = {} self._settings[parentId][item.data(0, Qt.UserRole)] = properties def setCurrentTreeItemByData(self, data): it = QTreeWidgetItemIterator(self.ui.treeWidget) while it.value(): if it.value().data(0, Qt.UserRole) == data: self.ui.treeWidget.setCurrentItem(it.value()) return True it += 1 return False def currentTemplateChanged(self, index=None): cbox = self.ui.comboBox_Template templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole) if templateType == self.templateType: return # hide items unsupported by template tree = self.ui.treeWidget for i, id in enumerate(ObjectTreeItem.topItemIds): hidden = (templateType == "sphere" and id != ObjectTreeItem.ITEM_CONTROLS) tree.topLevelItem(i).setHidden(hidden) # set current tree item if templateType == "sphere": tree.setCurrentItem( tree.topLevelItem( ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_CONTROLS))) elif self.lastTreeItemData is None or not self.setCurrentTreeItemByData( self.lastTreeItemData): # restore selection tree.setCurrentItem( tree.topLevelItem( ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_DEM)) ) # default selection for plain is DEM # display messages self.clearMessageBar() if templateType != "sphere": # show message if crs unit is degrees mapSettings = self.iface.mapCanvas().mapSettings( ) if QGis.QGIS_VERSION_INT >= 20300 else self.iface.mapCanvas( ).mapRenderer() if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]: self.showMessageBar( "The unit of current CRS is degrees, so terrain may not appear well.", QgsMessageBar.WARNING) self.templateType = templateType def currentObjectChanged(self, currentItem, previousItem): # save properties of previous item if previousItem and self.currentPage: self.saveProperties(previousItem, self.currentPage) self.currentItem = currentItem self.currentPage = None # hide text browser and all pages self.ui.textBrowser.hide() for page in self.pages.itervalues(): page.hide() parent = currentItem.parent() if parent is None: topItemIndex = currentItem.data(0, Qt.UserRole) pageType = self.topItemPages.get(topItemIndex, ppages.PAGE_NONE) page = self.pages.get(pageType, None) if page is None: self.showDescription(topItemIndex) return page.setup(self._settings.get(topItemIndex)) page.show() else: parentId = parent.data(0, Qt.UserRole) layerId = currentItem.data(0, Qt.UserRole) layer = QgsMapLayerRegistry.instance().mapLayer(unicode(layerId)) if layer is None: return layerType = layer.type() if layerType == QgsMapLayer.RasterLayer: page = self.pages[ppages.PAGE_DEM] page.setup( self._settings.get(parentId, {}).get(layerId, None), layer, False) elif layerType == QgsMapLayer.VectorLayer: page = self.pages[ppages.PAGE_VECTOR] page.setup( self._settings.get(parentId, {}).get(layerId, None), layer) else: return page.show() self.currentPage = page def objectItemChanged(self, item, column): parent = item.parent() if parent is None: return # checkbox of optional layer checked/unchecked if item == self.currentItem: if self.currentPage: # update enablement of property widgets self.currentPage.itemChanged(item) else: # select changed item self.ui.treeWidget.setCurrentItem(item) # set visible property #visible = item.data(0, Qt.CheckStateRole) == Qt.Checked #parentId = parent.data(0, Qt.UserRole) #layerId = item.data(0, Qt.UserRole) #self._settings.get(parentId, {}).get(layerId, {})["visible"] = visible def primaryDEMChanged(self, layerId): tree = self.ui.treeWidget parent = tree.topLevelItem( ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_OPTDEM)) tree.blockSignals(True) for i in range(parent.childCount()): item = parent.child(i) isPrimary = item.data(0, Qt.UserRole) == layerId item.setDisabled(isPrimary) tree.blockSignals(False) def showDescription(self, topItemIndex): fragment = { ObjectTreeItem.ITEM_OPTDEM: "additional-dem", ObjectTreeItem.ITEM_POINT: "point", ObjectTreeItem.ITEM_LINE: "line", ObjectTreeItem.ITEM_POLYGON: "polygon" }.get(topItemIndex) url = "http://qgis2threejs.readthedocs.org/en/docs-release/ExportSettings.html" if fragment: url += "#" + fragment html = '<a href="{0}">Online Help</a> about this item'.format(url) self.ui.textBrowser.setHtml(html) self.ui.textBrowser.show() def numericFields(self, layer): # get attributes of a sample feature and create numeric field name list numeric_fields = [] f = QgsFeature() layer.getFeatures().nextFeature(f) for field in f.fields(): isNumeric = False try: float(f.attribute(field.name())) isNumeric = True except ValueError: pass if isNumeric: numeric_fields.append(field.name()) return numeric_fields def mapTo3d(self): canvas = self.iface.mapCanvas() mapSettings = canvas.mapSettings( ) if QGis.QGIS_VERSION_INT >= 20300 else canvas.mapRenderer() world = self._settings.get(ObjectTreeItem.ITEM_WORLD, {}) bs = float(world.get("lineEdit_BaseSize", def_vals.baseSize)) ve = float(world.get("lineEdit_zFactor", def_vals.zExaggeration)) vs = float(world.get("lineEdit_zShift", def_vals.zShift)) return MapTo3D(mapSettings, bs, ve, vs) def progress(self, percentage=None, statusMsg=None): ui = self.ui if percentage is not None: ui.progressBar.setValue(percentage) if percentage == 100: ui.progressBar.setVisible(False) ui.label_Status.setText("") else: ui.progressBar.setVisible(True) if statusMsg is not None: ui.label_Status.setText(statusMsg) ui.label_Status.repaint() QgsApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def run(self): self.endPointSelection() ui = self.ui filename = ui.lineEdit_OutputFilename.text() # ""=Temporary file if filename and os.path.exists(filename): if QMessageBox.question( self, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok: return # export to web (three.js) export_settings = ExportSettings(self.pluginManager, self.localBrowsingMode) export_settings.loadSettings(self.settings()) export_settings.setMapCanvas(self.iface.mapCanvas()) err_msg = export_settings.checkValidity() if err_msg is not None: QMessageBox.warning(self, "Qgis2threejs", err_msg or "Invalid settings") return ui.pushButton_Run.setEnabled(False) ui.toolButton_Settings.setVisible(False) self.clearMessageBar() self.progress(0) if export_settings.exportMode == ExportSettings.PLAIN_MULTI_RES: # update quads and point on map canvas self.createRubberBands(export_settings.baseExtent, export_settings.quadtree()) # export ret = exportToThreeJS(export_settings, self.iface.legendInterface(), self.objectTypeManager, self.progress) self.progress(100) ui.pushButton_Run.setEnabled(True) if not ret: ui.toolButton_Settings.setVisible(True) return self.clearRubberBands() # store last selections settings = QSettings() settings.setValue("/Qgis2threejs/lastTemplate", export_settings.templatePath) settings.setValue("/Qgis2threejs/lastControls", export_settings.controls) # open web browser if not tools.openHTMLFile(export_settings.htmlfilename): ui.toolButton_Settings.setVisible(True) return # close dialog QDialog.accept(self) def reject(self): # save properties of current object item = self.ui.treeWidget.currentItem() if item and self.currentPage: self.saveProperties(item, self.currentPage) self.endPointSelection() self.clearRubberBands() QDialog.reject(self) def help(self): url = "http://qgis2threejs.readthedocs.org/" import webbrowser webbrowser.open(url, new=2) # new=2: new tab if possible def startPointSelection(self): canvas = self.iface.mapCanvas() if self.previousMapTool != self.mapTool: self.previousMapTool = canvas.mapTool() canvas.setMapTool(self.mapTool) self.pages[ppages.PAGE_DEM].toolButton_PointTool.setVisible(False) def endPointSelection(self): self.mapTool.reset() if self.previousMapTool is not None: self.iface.mapCanvas().setMapTool(self.previousMapTool) def mapToolSet(self, mapTool): return #TODO: unstable if mapTool != self.mapTool and self.currentPage is not None: if self.currentPage.pageType == ppages.PAGE_DEM and self.currentPage.isPrimary: self.currentPage.toolButton_PointTool.setVisible(True) def createRubberBands(self, baseExtent, quadtree): self.clearRubberBands() # create quads with rubber band self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line) self.rb_quads.setColor(Qt.blue) self.rb_quads.setWidth(1) quads = quadtree.quads() for quad in quads: geom = baseExtent.subrectangle(quad.rect).geometry() self.rb_quads.addGeometry(geom, None) self.log("Quad count: %d" % len(quads)) if not quadtree.focusRect: return # create a point with rubber band if quadtree.focusRect.width() == 0 or quadtree.focusRect.height() == 0: npt = quadtree.focusRect.center() self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rb_point.setColor(Qt.red) self.rb_point.addPoint(baseExtent.point(npt)) def clearRubberBands(self): # clear quads and point if self.rb_quads: self.iface.mapCanvas().scene().removeItem(self.rb_quads) self.rb_quads = None if self.rb_point: self.iface.mapCanvas().scene().removeItem(self.rb_point) self.rb_point = None def browseClicked(self): directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0] if not directory: directory = QDir.homePath() filename = QFileDialog.getSaveFileName( self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite) if not filename: return # append .html extension if filename doesn't have either .html or .htm if filename[-5:].lower() != ".html" and filename[-4:].lower( ) != ".htm": filename += ".html" self.ui.lineEdit_OutputFilename.setText(filename) def log(self, msg): if debug_mode: qDebug(msg)
class MultiLayerSelection(QgsMapTool): finished = QtCore.pyqtSignal(list) def __init__(self, canvas, iface): """ Tool Behaviours: (all behaviours start edition, except for rectangle one) 1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. The selection is done with the following priority: Point, Line then Polygon. Selection is only done in visible layer. 2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1. 3- Right Click: Opens feature form 4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition follows priority of item 1; 5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangl'e are added to selection """ self.iface = iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.hoverRubberBand = QgsRubberBand(self.canvas, QGis.Polygon) mFillColor = QColor( 254, 178, 76, 63 ) self.rubberBand.setColor(mFillColor) self.hoverRubberBand.setColor(QColor( 255, 0, 0, 90 )) self.rubberBand.setWidth(1) self.reset() self.blackList = self.getBlackList() self.cursorChanged = False self.cursorChangingHotkey = QtCore.Qt.Key_Alt self.menuHovered = False # indicates hovering actions over context menu def keyPressEvent(self, e): """ Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (Alt). """ if e.key() == self.cursorChangingHotkey and not self.cursorChanged: self.cursorChanged = True QtGui.QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor)) else: self.cursorChanged = False QtGui.QApplication.restoreOverrideCursor() def getBlackList(self): settings = QSettings() settings.beginGroup('PythonPlugins/DsgTools/Options') valueList = settings.value('valueList') if valueList: valueList = valueList.split(';') return valueList else: return ['moldura'] def reset(self): """ Resets rubber band. """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QGis.Polygon) def keyPressEvent(self, e): """ Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (F2). """ if e.key() == self.cursorChangingHotkey and not self.cursorChanged: self.cursorChanged = True QtGui.QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor)) else: self.cursorChanged = False QtGui.QApplication.restoreOverrideCursor() def canvasMoveEvent(self, e): """ Used only on rectangle select. """ if self.menuHovered: # deactivates rubberband when the context menu is "destroyed" self.hoverRubberBand.reset(QGis.Polygon) if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates( e.pos() ) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Builds rubberband rect. """ self.rubberBand.reset(QGis.Polygon) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """ Builds rectangle from self.startPoint and self.endPoint """ if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setAction(self, action): self.toolAction = action self.toolAction.setCheckable(True) def canvasReleaseEvent(self, e): """ After the rectangle is built, here features are selected. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: firstGeom = self.checkSelectedLayers() self.isEmittingPoint = False r = self.rectangle() if r is None: return layers = self.canvas.layers() for layer in layers: #ignore layers on black list and features that are not vector layers if not isinstance(layer, QgsVectorLayer) or (self.layerHasPartInBlackList(layer.name())): continue if firstGeom is not None and layer.geometryType() != firstGeom: # if there are features already selected, shift will only get the same type geometry # if more than one ty of geometry is present, only the strongest will be selected continue #builds bbRect and select from layer, adding selection bbRect = self.canvas.mapSettings().mapToLayerCoordinates(layer, r) layer.select(bbRect, True) self.rubberBand.hide() def canvasPressEvent(self, e): """ Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = True self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) else: self.isEmittingPoint = False self.createContextMenu(e) def getCursorRect(self, e): """ Calculates small cursor rectangle around mouse position. Used to facilitate operations """ p = self.toMapCoordinates(e.pos()) w = self.canvas.mapUnitsPerPixel() * 10 return QgsRectangle(p.x()-w, p.y()-w, p.x()+w, p.y()+w) def layerHasPartInBlackList(self, lyrName): """ Verifies if terms in black list appear on lyrName """ for item in self.getBlackList(): if item.lower() in lyrName.lower(): return True return False def getPrimitiveDict(self, e, hasControlModifier=False): """ Builds a dict with keys as geometryTypes of layer, which are QGis.Point (value 0), QGis.Line (value 1) or QGis.Polygon (value 2), and values as layers from self.iface.legendInterface().layers(). When self.iface.legendInterface().layers() is called, a list of layers ordered according to lyr order in TOC is returned. """ #these layers are ordered by view order primitiveDict = dict() firstGeom = self.checkSelectedLayers() for lyr in self.iface.legendInterface().layers(): #ordered layers #layer types other than VectorLayer are ignored, as well as layers in black list and layers that are not visible if (lyr.type() != QgsMapLayer.VectorLayer) or (self.layerHasPartInBlackList(lyr.name())) or not self.iface.legendInterface().isLayerVisible(lyr): continue if hasControlModifier and (not firstGeom) and (not primitiveDict.keys() or lyr.geometryType() < firstGeom): firstGeom = lyr.geometryType() geomType = lyr.geometryType() if geomType not in primitiveDict.keys(): primitiveDict[geomType] = [] #removes selection if (not hasControlModifier and e.button() == QtCore.Qt.LeftButton) or (hasControlModifier and e.button() == QtCore.Qt.RightButton): lyr.removeSelection() primitiveDict[geomType].append(lyr) if hasControlModifier and firstGeom in [0, 1, 2]: return { firstGeom : primitiveDict[firstGeom] } else: return primitiveDict def deactivate(self): """ Deactivate tool. """ QtGui.QApplication.restoreOverrideCursor() self.hoverRubberBand.reset(QGis.Polygon) try: if self.toolAction: self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) except: pass def activate(self): """ Activate tool. """ if self.toolAction: self.toolAction.setChecked(True) QgsMapTool.activate(self) def setSelectionFeature(self, layer, feature, selectAll=False, setActiveLayer=False): """ Selects a given feature on canvas. :param layer: (QgsVectorLayer) layer containing the target feature. :param feature: (QgsFeature) taget feature to be selected. :param selectAll: (bool) indicates whether or not this fuction was called from a select all command. so it doesn't remove selection from those that are selected already from the list. :param setActiveLayer: (bool) indicates whether method should set layer as active. """ idList = layer.selectedFeaturesIds() # self.iface.setActiveLayer(layer) # esp acho q o problema eh aqui # layer.startEditing() featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) if setActiveLayer: layer.startEditing() if self.iface.activeLayer() != layer: self.iface.setActiveLayer(layer) return def setSelectionListFeature(self, dictLayerFeature, selectAll=True): """ Selects all features on canvas of a given dict. :param dictLayerFeature: (dict) dict of layers/features to be selected. :param selectAll: (bool) indicates if "All"-command comes from a "Select All". In that case, selected features won't be deselected. """ for layer in dictLayerFeature.keys(): geomType = layer.geometryType() # ID list of features already selected idList = layer.selectedFeaturesIds() # restart feature ID list for each layer featIdList = [] for feature in dictLayerFeature[layer]: featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) layer.startEditing() # last layer is set active and if self.iface.activeLayer() != layer: self.iface.setActiveLayer(layer) def openMultipleFeatureForm(self, dictLayerFeature): """ Opens all features Feature Forms of a given list. :param dictLayerFeature: (dict) dict of layers/features to have their feature form exposed. """ for layer, features in dictLayerFeature.iteritems(): for feat in features: self.iface.openFeatureForm(layer, feat, showModal=False) def filterStrongestGeometry(self, dictLayerFeature): """ Filter a given dict of features for its strongest geometry. :param dictLayerFeature: (dict) a dict of layers and its features to be filtered. :return: (dict) filtered dict with only layers of the strongest geometry on original dict. """ strongest_geometry = 3 outDict = dict() if dictLayerFeature: for lyr in dictLayerFeature.keys(): # to retrieve strongest geometry value if strongest_geometry > lyr.geometryType(): strongest_geometry = lyr.geometryType() if strongest_geometry == 0: break for lyr in dictLayerFeature.keys(): if lyr.geometryType() == strongest_geometry: outDict[lyr] = dictLayerFeature[lyr] return outDict def createRubberBand(self, feature, layer, geom): """ Creates a rubber band around from a given a standard feature string. :param feature: taget feature to be highlighted :param layer: layer containing the target feature :param geom: int indicating geometry type of target feature """ if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) self.hoverRubberBand.addGeometry(feature.geometry(), layer) # to inform the code that menu has been hovered over self.menuHovered = True def createMultipleRubberBand(self, dictLayerFeature): """ Creates rubberbands around features. :param dictLayerFeature: (dict) dict of layer/features to have rubberbands built around. """ # only one type of geometry at a time will have rubberbands around it geom = dictLayerFeature.keys()[0].geometryType() if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) for layer, features in dictLayerFeature.iteritems(): for feat in features: self.hoverRubberBand.addGeometry(feat.geometry(), layer) self.menuHovered = True def checkSelectedLayers(self): """ Checks if there are layers selected on canvas. If there are, returns the geometry type of selected feature(s). If more than one type of feature is selected, the "strongest" geometry is returned. """ geom = None for layer in self.iface.legendInterface().layers(): if isinstance(layer, QgsVectorLayer): selection = layer.selectedFeatures() if len(selection): if geom == None: geom = layer.geometryType() continue elif layer.geometryType() < geom: geom = layer.geometryType() continue return geom def addCallBackToAction(self, action, onTriggeredAction, onHoveredAction=None): """ Adds action the command to the action. If onHoveredAction is given, signal "hovered" is applied with given action. :param action: (QAction) associated with target context menu. :param onTriggeredAction: (object) action to be executed when the given action is triggered. :param onHoveredAction: (object) action to be executed whilst the given action is hovered. """ action.triggered[()].connect(onTriggeredAction) if onHoveredAction: action.hovered[()].connect(onHoveredAction) def getCallback(self, e, layer, feature, geomType=None, selectAll=True): """ Gets the callback for an action. :param e: (QMouseEvent) mouse event on canvas. :param layer: (QgsVectorLayer) layer to be treated. :param feature: (QgsFeature) feature to be treated. :param geomType: (int) code indicating layer geometry type. It is retrieved OTF in case it's not given. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ if not geomType: geomType = layer.geometryType() if e.button() == QtCore.Qt.LeftButton: # line added to make sure the action is associated with current loop value, # lambda function is used with standard parameter set to current loops value. triggeredAction = lambda t=[layer, feature] : self.setSelectionFeature(t[0], feature=t[1], selectAll=selectAll, setActiveLayer=True) hoveredAction = lambda t=[layer, feature] : self.createRubberBand(feature=t[1], layer=t[0], geom=geomType) elif e.button() == QtCore.Qt.RightButton: selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: triggeredAction = lambda layer=layer : self.iface.setActiveLayer(layer) hoveredAction = None else: triggeredAction = lambda t=[layer, feature] : self.iface.openFeatureForm(t[0], t[1], showModal=False) hoveredAction = lambda t=[layer, feature] : self.createRubberBand(feature=t[1], layer=t[0], geom=geomType) return triggeredAction, hoveredAction def getCallbackMultipleFeatures(self, e, dictLayerFeature, selectAll=True): """ Sets the callback of an action with a list features as target. :param e: (QMouseEvent) mouse event on canvas. :param dictLayerFeature: (dict) dictionary containing layers/features to be treated. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ # setting the action for the "All" options if e.button() == QtCore.Qt.LeftButton: triggeredAction = lambda t=dictLayerFeature: self.setSelectionListFeature(dictLayerFeature=t, selectAll=selectAll) else: triggeredAction = lambda t=dictLayerFeature: self.openMultipleFeatureForm(dictLayerFeature=t) # to trigger "Hover" signal on QMenu for the multiple options hoveredAction = lambda t=dictLayerFeature : self.createMultipleRubberBand(dictLayerFeature=t) return triggeredAction, hoveredAction def createSubmenu(self, e, parentMenu, menuDict, genericAction, selectAll): """ Creates a submenu in a given parent context menu and populates it, with classes/feature sublevels from the menuDict. :param e: (QMouseEvent) mouse event on canvas. If menuDict has only 1 class in it, method will populate parent QMenu. :param parentMenu: (QMenu) menu containing the populated submenu :param menuDict: (dict) dictionary containing all classes and their features to be filled into submenu. :param genericAction: (str) text to be shown into generic action description on the outter level of submenu. :return: (dict) mapping of classes and their own QMenu object. """ # creating a dict to handle all "menu" for each class submenuDict = dict() # sort the layers from diciotnary classNameDict = { cl.name() : cl for cl in menuDict.keys() } layers = sorted(classNameDict.keys()) for className in layers: # menu for features of each class cl = classNameDict[className] geomType = cl.geometryType() # get layer database name dsUri = cl.dataProvider().dataSourceUri() temp = [] if '/' in dsUri or '\\' in dsUri: db_name = dsUri.split("|")[0] if "|" in dsUri else dsUri # data source is a file, not a postgres database dbIsFile = True elif 'memory' in dsUri: db_name = self.tr(u'{0} (Memory Layer)').format(className) dbIsFile = True else: db_name = dsUri.split("'")[1] dbIsFile = False if len(menuDict) == 1: # if dictionaty has only 1 class, no need for an extra QMenu - features will be enlisted directly # order features by ID to be displayer ordered featDict = { feat.id() : feat for feat in menuDict[cl] } orderedFeatIdList = sorted(featDict.keys()) for featId in orderedFeatIdList: feat = featDict[featId] if dbIsFile: s = u'{0} (feat_id = {1})'.format(db_name, featId) else: s = u'{0}.{1} (feat_id = {2})'.format(db_name, className, featId) # inserting action for each feature action = parentMenu.addAction(s) triggeredAction, hoveredAction = self.getCallback(e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # inserting generic action, if necessary if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = parentMenu.addAction(self.tr(u"{0} From Class {1}").format(genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # there is no mapping of class to be exposed, only information added to parent QMenu itself return dict() if dbIsFile: title = db_name else: title = '{0}.{1}'.format(db_name, className) submenuDict[cl] = QtGui.QMenu(title=title, parent=parentMenu) parentMenu.addMenu(submenuDict[cl]) # inserting an entry for every feature of each class in its own context menu # order features by ID to be displayer ordered featDict = { feat.id() : feat for feat in menuDict[cl] } orderedFeatIdList = sorted(featDict.keys()) for featId in orderedFeatIdList: feat = featDict[featId] s = u'feat_id = {0}'.format(featId) action = submenuDict[cl].addAction(s) triggeredAction, hoveredAction = self.getCallback(e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # set up list for the "All"-commands temp.append([cl, feat, geomType]) # adding generic action for each class if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = submenuDict[cl].addAction(self.tr(u"{0} From Class {1}").format(genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature={ cl : menuDict[cl] }, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) return submenuDict def setContextMenuStyle(self, e, dictMenuSelected, dictMenuNotSelected): """ Defines how many "submenus" the context menu should have. There are 3 context menu scenarios to be handled: :param e: (QMouseEvent) mouse event on canvas. :param dictMenuSelected: (dict) dictionary of classes and its selected features being treatead. :param dictMenuNotSelected: (dict) dictionary of classes and its non selected features being treatead. """ # finding out filling conditions selectedDict = bool(dictMenuSelected) notSelectedDict = bool(dictMenuNotSelected) # finding out if one of either dictionaty are filled ("Exclusive or") selectedXORnotSelected = (selectedDict != notSelectedDict) # setting "All"-command name if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Feature Forms') else: genericAction = self.tr('Select All Features') # in case one of given dict is empty if selectedXORnotSelected: if selectedDict: menuDict, menu = dictMenuSelected, QtGui.QMenu(title=self.tr('Selected Features')) genericAction = self.tr('Deselect All Features') # if the dictionary is from selected features, we want commands to be able to deselect them selectAll = False else: menuDict, menu = dictMenuNotSelected, QtGui.QMenu(title=self.tr('Not Selected Features')) genericAction = self.tr('Select All Features') # if the dictionary is from non-selected features, we want commands to be able to select them selectAll = True if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Feature Forms') self.createSubmenu(e=e, parentMenu=menu, menuDict=menuDict, genericAction=genericAction, selectAll=selectAll) if len(menuDict) != 1 and len(menuDict.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = menu.addAction(genericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) elif selectedDict: # if both of them is empty one more QMenu level is added menu = QtGui.QMenu() selectedMenu = QtGui.QMenu(title=self.tr('Selected Features')) notSelectedMenu = QtGui.QMenu(title=self.tr('Not Selected Features')) menu.addMenu(selectedMenu) menu.addMenu(notSelectedMenu) selectedGenericAction = self.tr('Deselect All Features') notSelectedGenericAction = self.tr('Select All Features') # selectAll is set to True as now we want command to Deselect Features in case they are selected self.createSubmenu(e=e, parentMenu=selectedMenu, menuDict=dictMenuSelected, genericAction=selectedGenericAction, selectAll=False) if len(dictMenuSelected) != 1 and len(dictMenuSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = selectedMenu.addAction(selectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=dictMenuSelected, selectAll=False) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) self.createSubmenu(e=e, parentMenu=notSelectedMenu, menuDict=dictMenuNotSelected, genericAction=notSelectedGenericAction, selectAll=True) if len(dictMenuNotSelected) != 1 and len(dictMenuNotSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = notSelectedMenu.addAction(notSelectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=dictMenuNotSelected, selectAll=True) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) menu.exec_(self.canvas.viewport().mapToGlobal(e.pos())) def checkSelectedFeaturesOnDict(self, menuDict): """ Checks all selected features from a given dictionary ( { (QgsVectorLayer)layer : [ (QgsFeature)feat ] } ). :param menuDict: (dict) dictionary with layers and their features to be analyzed. :return: (tuple-of-dict) both dictionaries of selected and non-selected features of each layer. """ selectedFeaturesDict, notSelectedFeaturesDict = dict(), dict() for cl in menuDict.keys(): selectedFeats = [f.id() for f in cl.selectedFeatures()] for feat in menuDict[cl]: if feat.id() in selectedFeats: if cl not in selectedFeaturesDict.keys(): selectedFeaturesDict[cl] = [feat] else: selectedFeaturesDict[cl].append(feat) else: if cl not in notSelectedFeaturesDict.keys(): notSelectedFeaturesDict[cl] = [feat] else: notSelectedFeaturesDict[cl].append(feat) return selectedFeaturesDict, notSelectedFeaturesDict def reprojectSearchArea(self, layer, geom): """ Reprojects search area if necessary, according to what is being searched. :param layer: (QgsVectorLayer) layer which target rectangle has to have same SRC. :param geom: (QgsRectangle) rectangle representing search area. """ #geom always have canvas coordinates epsg = self.canvas.mapSettings().destinationCrs().authid() #getting srid from something like 'EPSG:31983' srid = layer.crs().authid() if epsg == srid: return geom crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(srid) # Creating a transformer coordinateTransformer = QgsCoordinateTransform(crsSrc, crsDest) # here we have to put authid, not srid auxGeom = QgsGeometry.fromRect(geom) auxGeom.transform(coordinateTransformer) return auxGeom.boundingBox() def createContextMenu(self, e): """ Creates the context menu for overlapping layers. :param e: mouse event caught from canvas. """ selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: firstGeom = self.checkSelectedLayers() # setting a list of features to iterate over layerList = self.getPrimitiveDict(e, hasControlModifier=selected) layers = [] for key in layerList.keys(): layers += layerList[key] if layers: rect = self.getCursorRect(e) lyrFeatDict = dict() for layer in layers: if not isinstance(layer, QgsVectorLayer): continue geomType = layer.geometryType() # iterate over features inside the mouse bounding box bbRect = self.canvas.mapSettings().mapToLayerCoordinates(layer, rect) for feature in layer.getFeatures(QgsFeatureRequest(bbRect)): geom = feature.geometry() if geom: searchRect = self.reprojectSearchArea(layer, rect) if selected: # if Control was held, appending behaviour is different if not firstGeom: firstGeom = geomType elif firstGeom > geomType: firstGeom = geomType if geomType == firstGeom and geom.intersects(searchRect): # only appends features if it has the same geometry as first selected feature if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] else: if geom.intersects(searchRect): if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] lyrFeatDict = self.filterStrongestGeometry(lyrFeatDict) if lyrFeatDict: moreThanOneFeat = lyrFeatDict.values() and len(lyrFeatDict.values()) > 1 or len(lyrFeatDict.values()[0]) > 1 if moreThanOneFeat: # if there are overlapping features (valid candidates only) selectedFeaturesDict, notSelectedFeaturesDict = self.checkSelectedFeaturesOnDict(menuDict=lyrFeatDict) self.setContextMenuStyle(e=e, dictMenuSelected=selectedFeaturesDict, dictMenuNotSelected=notSelectedFeaturesDict) else: layer = lyrFeatDict.keys()[0] feature = lyrFeatDict[layer][0] selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if e.button() == QtCore.Qt.LeftButton: # if feature is selected, we want it to be de-selected self.setSelectionFeature(layer=layer, feature=feature, selectAll=False, setActiveLayer=True) elif selected: if self.iface.activeLayer() != layer: self.iface.setActiveLayer(layer) else: self.iface.openFeatureForm(layer, feature, showModal=False)