def _draw_rubberband(self, geometry, colour, width): """Draw a rubber band on the canvas. .. versionadded: 2.2.0 :param geometry: Extent that the rubber band should be drawn for. :type geometry: QgsGeometry :param colour: Colour for the rubber band. :type colour: QColor :param width: The width for the rubber band pen stroke. :type width: int :returns: Rubber band that should be set to the extent. :rtype: QgsRubberBand """ # noinspection PyArgumentList rubber_band = QgsRubberBand(self._map_canvas, geometryType=QGis.Polygon) rubber_band.setBrushStyle(Qt.NoBrush) rubber_band.setColor(colour) rubber_band.setWidth(width) rubber_band.setToGeometry(geometry, None) return rubber_band
def _draw_rubberband(self, geometry, colour, width): """Draw a rubber band on the canvas. .. versionadded: 2.2.0 :param geometry: Extent that the rubber band should be drawn for. :type geometry: QgsGeometry :param colour: Colour for the rubber band. :type colour: QColor :param width: The width for the rubber band pen stroke. :type width: int :returns: Rubber band that should be set to the extent. :rtype: QgsRubberBand """ # noinspection PyArgumentList rubber_band = QgsRubberBand( self._map_canvas, geometryType=QGis.Polygon) rubber_band.setBrushStyle(Qt.NoBrush) rubber_band.setColor(colour) rubber_band.setWidth(width) rubber_band.setToGeometry(geometry, None) return rubber_band
class CoordTool(QgsMapTool): def __init__(self, iface): QgsMapTool.__init__(self, iface.mapCanvas()) self.canvas = iface.mapCanvas() self.iface = iface self.status = 0 self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.rb.setColor(QColor(255, 70, 0, 200)) self.rb.setWidth(5) self.rb.setBrushStyle(Qt.NoBrush) def canvasReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.status == 0: self.rb.reset(QgsWkbTypes.LineGeometry) self.status = 1 self.rb.addPoint(self.toMapcoorinates(event.pos())) else: if self.rb.numberOfVertices() > 1: self.status = 0 current_layer = self.iface.mapCanvas().currentLayer() pr = current_layer.dataProvider() caps = current_layer.dataProvider().capabilities() if caps & QgsVectorDataProvider.AddFeatures: feat = QgsFeature(current_layer.fields()) feat.setGeometry(self.rb.asGemoetry()) pr.addFeature(feat) current_layer.updateExtents() current_layer.commitChanges() current_layer.triggerRepaint() def canvasMoveEvent(self, event): if self.rb.numberOfVertices() > 0 and self.status == 1: self.rb.removeLastPoint(0) self.rb.addPoint(self.toMapCoordinates(event.pos()))
def build_rubber_band(self, points): rubber_band = QgsRubberBand(self.canvas(), False) # False = not a polygon rubber_band.setToGeometry(QgsGeometry.fromPolyline(points), None) # for point in points: # rubber_band.addPoint(point) rubber_band.setColor(QColor(255, 128, 128)) rubber_band.setWidth(1) rubber_band.setBrushStyle(Qt.Dense4Pattern) return rubber_band
class CurrentSelection(QgsRubberBand): """ Position marker for the current location in the viewer. """ class AniObject(QObject): def __init__(self, band): super(CurrentSelection.AniObject, self).__init__() self.color = QColor() @pyqtProperty(int) def alpha(self): return self.color.alpha() @alpha.setter def alpha(self, value): self.color.setAlpha(value) def __init__(self, canvas): super(CurrentSelection, self).__init__(canvas) self.outline = QgsRubberBand(canvas) self.outline.setBrushStyle(Qt.NoBrush) self.outline.setWidth(5) self.outline.setIconSize(30) self.aniobject = CurrentSelection.AniObject(self) self.anim = QPropertyAnimation(self.aniobject, "alpha") self.anim.setDuration(500) self.anim.setStartValue(50) self.anim.setEndValue(100) self.anim.valueChanged.connect(self.value_changed) def setOutlineColour(self, color): self.outline.setColor(color) def setToGeometry(self, geom, layer): super(CurrentSelection, self).setToGeometry(geom, layer) self.outline.setToGeometry(geom, layer) self.anim.stop() self.anim.start() def reset(self, geomtype=QGis.Line): super(CurrentSelection, self).reset(geomtype) self.outline.reset(geomtype) self.anim.stop() def value_changed(self, value): self.setColor(self.aniobject.color) self.update() def setColor(self, color): self.aniobject.color = color super(CurrentSelection, self).setColor(color)
class RectangleMapTool(QgsMapToolEmitPoint): boxCreated = pyqtSignal(QgsRectangle) def __init__(self, canvas): self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes().GeometryType(2)) self.rubberBand.setColor(Qt.red) self.rubberBand.setBrushStyle(Qt.Dense6Pattern) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes().GeometryType(2)) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() if r is not None: self.boxCreated.emit(r) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes().GeometryType(2)) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(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): 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 self.rubberBand.hide() srsCrs = self.canvas.mapSettings().destinationCrs() dstCrs = QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId) coordinateTransformer = QgsCoordinateTransform(srsCrs, dstCrs, QgsProject.instance()) rect = coordinateTransformer.transform( QgsRectangle(self.startPoint, self.endPoint)) return rect
class DockEditorDialog(QtGui.QDockWidget, Ui_DockEditor): def __init__(self, iface, mapCanvas): self.iface = iface QtGui.QDockWidget.__init__(self) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) #self.setAttribute(Qt.WA_DeleteOnClose) self.iface.addDockWidget(Qt.RightDockWidgetArea, self) self.mapCanvas = mapCanvas # rubber bands self.featureRubber = QgsRubberBand(self.mapCanvas, False) self.updateFeatureRubber() # Gui defaults self.kwaliteitsklasseCombo.setVisible(False) self.onderzochtJaarSpinBox.setVisible(False) self.hersteldJaarSpinBox.setVisible(False) self.kwaliteitsklasseLabel.setVisible(False) self.monitoringObjTekst.setVisible(False) # GUI signals connection self.actionButtonBox.button(QtGui.QDialogButtonBox.Save).clicked.connect(self.applySave) self.actionButtonBox.button(QtGui.QDialogButtonBox.Abort).clicked.connect(self.applyCancel) self.deleteButton.clicked.connect(self.applyDelete) self.newPandButton.clicked.connect(self.applyNewPand) self.idButton.clicked.connect(self.zoomToFeature) self.adresLinkButton.clicked.connect(self.showVerblijfsObj) self.verseonLinkButton.clicked.connect(self.getVerseonInfo) self.lkiLinkButton.clicked.connect(self.showLkiInfo) # force Dutch for QDialogButtonBox self.actionButtonBox.button(QtGui.QDialogButtonBox.Save).setText("Opslaan") self.actionButtonBox.button(QtGui.QDialogButtonBox.Abort).setText("Annuleer"); # enable save button when value changes self.bezitCombo.currentIndexChanged.connect(self.actionButtonBoxEnable) self.kwaliteitsklasseCombo.currentIndexChanged.connect(self.actionButtonBoxEnable) self.onderzochtCheckBox.stateChanged.connect(self.actionButtonBoxEnable) self.onderzochtJaarSpinBox.valueChanged.connect(self.actionButtonBoxEnable) self.hersteldCheckBox.stateChanged.connect(self.actionButtonBoxEnable) self.hersteldJaarSpinBox.valueChanged.connect(self.actionButtonBoxEnable) self.projectCombo.editTextChanged.connect(self.actionButtonBoxEnable) self.richtlijnCombo.editTextChanged.connect(self.actionButtonBoxEnable) self.handhavingCheckBox.stateChanged.connect(self.actionButtonBoxEnable) self.typefundCombo.editTextChanged.connect(self.actionButtonBoxEnable) self.paallengteSpinBox.valueChanged.connect(self.actionButtonBoxEnable) self.houtsoortCombo.editTextChanged.connect(self.actionButtonBoxEnable) self.dekkingSpinBox.valueChanged.connect(self.actionButtonBoxEnable) self.droogstandCheckBox.stateChanged.connect(self.actionButtonBoxEnable) self.opmerkingText.textChanged.connect(self.actionButtonBoxEnable) self.setVisible(False) def showEvent(self, e): self.newGroupBox.hide() self.editGroupBox.hide() if (getVectorLayerByName(editLayerName) == None): self.errorFrame.show() else: self.errorFrame.hide() self.editLayer = getVectorLayerByName(editLayerName) self.adresLayer = getVectorLayerByName(adresLayerName) self.geomLayer = getVectorLayerByName(geomLayerName) self.editFeature = None self.updateCombos() def closeEvent(self, e): e.ignore() self.featureRubber.reset() self.hide() def zoomToFeature(self): box = self.selectedFeature.geometry().buffer(20, 4).boundingBox() self.iface.mapCanvas().setExtent(box) self.iface.mapCanvas().refresh() def updateCombos(self): if (self.editLayer): FieldCombo(self.bezitCombo, self.editLayer, initField="bezit") FieldCombo(self.kwaliteitsklasseCombo, self.editLayer, initField="kwaliteitsklasse") FieldCombo(self.projectCombo, self.editLayer, initField="project") FieldCombo(self.richtlijnCombo, self.editLayer, initField="richtlijn") FieldCombo(self.typefundCombo, self.editLayer, initField="funderingtype") FieldCombo(self.houtsoortCombo, self.editLayer, initField="houtsoort") def actionButtonBoxEnable(self): self.actionButtonBox.setDisabled(False) def featureSelected(self, layer, feature): self.layer = layer self.selectedFeature = feature initialGeometry = QgsGeometry(feature.geometry()) self.featureRubber.reset() self.featureRubber.setToGeometry(initialGeometry, layer) self.updateGui(feature) def updateGui(self, feature): # The important part: get the feature iterator with an expression request = QgsFeatureRequest().setFilterExpression ( u'"pand_id" = \'' + unicode(feature['gebouwnummer']) + '\'' ) request.setFlags( QgsFeatureRequest.NoGeometry ) features = self.geomLayer.getFeatures( request ) atr = None fields = self.geomLayer.pendingFields() field_names = [field.name() for field in fields] for elem in features: self.editFeature = elem atr = dict(zip(field_names, elem.attributes())) if atr is not None: atr = {i:j for i,j in atr.items() if j } self.newGroupBox.hide() self.editGroupBox.show() self.verwijderWidget.show() self.showValues(feature, atr) self.actionButtonBox.setDisabled(True) else: self.newGroupBox.show() self.editGroupBox.hide() self.verwijderWidget.hide() self.editFeature = None self.statusNieuwLabel.setText(unicode(feature['status'])) self.pandidLabel.setText(unicode(feature['gebouwnummer'])) def showValues(self, feature, atr): self.deleteCheckBox.setChecked(False) self.idText.setText(unicode(atr.get("pand_id", ""))) if (feature['vboj_count'] == 1): self.adresLinkButton.setText(BagadresString().request(unicode(feature['gebouwnummer']))) else: self.adresLinkButton.setText("Pand bevat " + unicode(feature['vboj_count']) + " adres(sen)") self.setComboTekst(self.bezitCombo, atr.get("bezit", "")) if (atr.get("onderzocht", "")=="t"): self.onderzochtCheckBox.setChecked(True) self.onderzochtJaarSpinBox.setValue(atr.get("onderzocht_jaar", "")) else: self.onderzochtCheckBox.setChecked(False) self.setComboTekst(self.kwaliteitsklasseCombo, atr.get("kwaliteitsklasse", "")) if (atr.get("hersteld", "")=="t"): self.hersteldCheckBox.setChecked(True) self.hersteldJaarSpinBox.setValue(atr.get("hersteld_jaar", "")) else: self.hersteldCheckBox.setChecked(False) self.setComboTekst(self.projectCombo, atr.get("project", "")) self.setComboTekst(self.richtlijnCombo, atr.get("richtlijn", "")) self.handhavingCheckBox.setChecked(toBoolean(atr.get("fumon_monitoring", "f"))) self.monitoringObjTekst.setText(atr.get("fumon_objectcode", "")) self.setComboTekst(self.typefundCombo, atr.get("funderingtype", "")) self.paallengteSpinBox.setValue(atr.get("paallengte", 0)) self.setComboTekst(self.houtsoortCombo, atr.get("houtsoort", "")) self.dekkingSpinBox.setValue(atr.get("dekking", 0)) self.droogstandCheckBox.setChecked(toBoolean(atr.get("droogstand", "f"))) self.opmerkingText.setPlainText(atr.get("opmerking", "")) # set texts in UI featureStatus = unicode(feature['status']) featureBj = unicode(feature['bouwjaar']) self.statusText.setText(featureStatus) self.bouwjaarText.setText(featureBj) def showVerblijfsObj(self): feature = self.selectedFeature gebouwnummer = unicode(feature['gebouwnummer']) InfoBagDialog(gebouwnummer, self.iface) def getVerseonInfo(self): feature = self.selectedFeature gebouwnummer = unicode(feature['gebouwnummer']) InfoVerseonDialog(gebouwnummer, self.iface) def showLkiInfo(self): feature = self.selectedFeature wkt = feature.geometry().pointOnSurface().exportToWkt() PerceelinfoDialog(wkt, self.iface) def setComboTekst(self, combo, value): idx = combo.findText(value) combo.setCurrentIndex(idx) def updateFeatureRubber(self): self.featureRubber.setColor(featureRubberColor) self.featureRubber.setWidth(featureRubberSize) self.featureRubber.setBrushStyle(Qt.NoBrush) self.mapCanvas.refresh() def applySave(self): if not self.editLayer.isEditable(): self.editLayer.startEditing() layer = self.editLayer layer.beginEditCommand("Funderingsgegevens") try: if (self.editFeature == None): idx = layer.pendingFields().indexFromName("id") nextId = layer.maximumValue(idx) feat = QgsFeature() feat.setFields(layer.pendingFields(), True) feat.setAttribute(layer.fieldNameIndex( "pand_id" ), self.pandidLabel.text()) feat.setAttribute(layer.fieldNameIndex( "id" ), nextId + 1) layer.addFeature(feat, False) self.editFeature = feat fid = self.editFeature.id() layer.changeAttributeValue(fid, layer.fieldNameIndex( "pand_id" ), self.idText.text()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "bezit" ), self.bezitCombo.currentText()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "onderzocht" ), self.onderzochtCheckBox.isChecked()) if (self.onderzochtCheckBox.isChecked()): layer.changeAttributeValue(fid, layer.fieldNameIndex( "onderzocht_jaar" ), self.onderzochtJaarSpinBox.value()) else: layer.changeAttributeValue(fid, layer.fieldNameIndex( "onderzocht_jaar" ), None) layer.changeAttributeValue(fid, layer.fieldNameIndex( "kwaliteitsklasse" ), self.kwaliteitsklasseCombo.currentText()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "hersteld" ), self.hersteldCheckBox.isChecked()) if (self.hersteldCheckBox.isChecked()): layer.changeAttributeValue(fid, layer.fieldNameIndex( "hersteld_jaar" ), self.hersteldJaarSpinBox.value()) else: layer.changeAttributeValue(fid, layer.fieldNameIndex( "hersteld_jaar" ), None) layer.changeAttributeValue(fid, layer.fieldNameIndex( "project" ), self.projectCombo.currentText()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "richtlijn" ), self.richtlijnCombo.currentText()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "funderingtype" ), self.typefundCombo.currentText()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "paallengte" ), self.paallengteSpinBox.value()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "houtsoort" ), self.houtsoortCombo.currentText()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "dekking" ), self.dekkingSpinBox.value()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "droogstand" ), self.droogstandCheckBox.isChecked()) layer.changeAttributeValue(fid, layer.fieldNameIndex( "opmerking" ), self.opmerkingText.toPlainText()) except Exception, e: raise e layer.destroyEditCommand() self.iface.messageBar().pushMessage("Funderingsherstel", "Er is wat misgegaan tijdens het opslaan van dit BAG object!", level=QgsMessageBar.CRITICAL) else:
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 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 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 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 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 teamqgisDock(QDockWidget, Ui_teamqgis): dockRemoved = pyqtSignal(str) def __init__(self, iface, layer, currentFeature): self.iface = iface self.layer = layer self.proj = QgsProject.instance() self.renderer = self.iface.mapCanvas().mapRenderer() self.settings = MySettings() QDockWidget.__init__(self) self.setupUi(self) # Track attr warnings so they are not repeated for multiple items self.warned_attr_values = [] self.setWindowTitle("teamqgis: %s" % layer.name()) if layer.hasGeometryType() is False: self.panCheck.setChecked(False) self.panCheck.setEnabled(False) self.scaleCheck.setChecked(False) self.scaleCheck.setEnabled(False) self.previousButton.setArrowType(Qt.LeftArrow) self.nextButton.setArrowType(Qt.RightArrow) icon = QIcon(":/plugins/teamqgis/icons/openform.svg") self.editFormButton.setIcon(icon) # actions icon = QIcon(":/plugins/teamqgis/icons/action.svg") self.actionButton.setIcon(icon) self.attrAction = layer.actions() actions = [self.attrAction[i] for i in range(self.attrAction.size())] preferredAction = layer.customProperty("teamqgisPreferedAction", "") if preferredAction not in actions: dfltAction = self.attrAction.defaultAction() if dfltAction > len(actions): preferredAction = self.attrAction[dfltAction].name() preferredActionFound = False for i, action in enumerate(actions): qAction = QAction(QIcon(":/plugins/teamqgis/icons/action.svg"), action.name(), self) qAction.triggered.connect(lambda: self.doAction(i)) self.actionButton.addAction(qAction) if action.name() == preferredAction: self.actionButton.setDefaultAction(qAction) preferredActionFound = True if len(actions) == 0: self.actionButton.setEnabled(False) elif not preferredActionFound: self.actionButton.setDefaultAction(self.actionButton.actions()[0]) self.nameComboBoxes = [self.fieldOneNameComboBox, self.fieldTwoNameComboBox, self.fieldThreeNameComboBox] self.valueComboBoxes = [self.fieldOneValueComboBox, self.fieldTwoValueComboBox, self.fieldThreeValueComboBox] self.updateNameComboBoxes() # Restore saved nameComboBox current indices if they exist for nameComboBox in self.nameComboBoxes: fieldName = self.layer.customProperty("teamqgis" + nameComboBox.objectName()) if fieldName != None: nameComboBox.setCurrentIndex(nameComboBox.findText(fieldName)) self.rubber = QgsRubberBand(self.iface.mapCanvas()) self.selectionChanged() if currentFeature == self.listCombo.currentIndex(): self.on_listCombo_currentIndexChanged(currentFeature) else: self.listCombo.setCurrentIndex(currentFeature) self.layer.layerDeleted.connect(self.close) self.layer.selectionChanged.connect(self.selectionChanged) self.layer.layerModified.connect(self.layerChanged) self.layer.editingStopped.connect(self.editingStopped) self.layer.editingStarted.connect(self.editingStarted) QObject.connect(self.proj, SIGNAL("allowedClassesChanged()"), self.updateValueComboBoxes) def updateNameComboBoxes(self): fieldNameMap = self.layer.dataProvider().fieldNameMap() allFields = fieldNameMap.keys() if 'ID' in allFields: allFields.remove('ID') if 'FID' in allFields: allFields.remove('FID') for nameComboBox in self.nameComboBoxes: nameComboBox.clear() nameComboBox.addItems(allFields) def updateValueComboBoxes(self): feature = self.getCurrentItem() for (valueComboBox, nameComboBox) in zip(self.valueComboBoxes, self.nameComboBoxes): valueComboBox.clear() allowedClasses, hasAllowedClasses = self.proj.readListEntry("teamqgis", "allowedClasses") if hasAllowedClasses: valueComboBox.addItems(allowedClasses) attr_value = str(feature[nameComboBox.currentText()]) if (allowedClasses == None) or (attr_value not in allowedClasses) and (attr_value not in self.warned_attr_values): self.iface.messageBar().pushMessage("Class name not in allowed class list", 'Assign an allowed class or add "%s" to allowed class list'%attr_value, level=QgsMessageBar.WARNING, duration=3) self.warned_attr_values.append(attr_value) valueComboBox.addItem(attr_value) valueComboBox.setCurrentIndex(valueComboBox.findText(attr_value)) def setRubber(self, feature): self.rubber.setColor(self.settings.value("rubberColor")) self.rubber.setWidth(self.settings.value("rubberWidth")) ##self.rubber.setLineStyle(Qt.DotLine) self.rubber.setBrushStyle(Qt.NoBrush) self.rubber.setToGeometry(feature.geometry(), self.layer) def closeEvent(self, e): self.rubber.reset() self.layer.layerDeleted.disconnect(self.close) self.layer.selectionChanged.disconnect(self.selectionChanged) self.layer.layerModified.disconnect(self.layerChanged) self.layer.editingStopped.disconnect(self.editingStopped) self.layer.editingStarted.disconnect(self.editingStarted) if self.settings.value("saveSelectionInProject"): self.layer.setCustomProperty("teamqgisSelection", repr([])) self.dockRemoved.emit(self.layer.id()) def selectionChanged(self): self.cleanBrowserFields() self.rubber.reset() nItems = self.layer.selectedFeatureCount() if nItems < 1: self.close() self.layer.emit(SIGNAL("browserNoItem()")) return self.browseFrame.setEnabled(True) self.subset = self.layer.selectedFeaturesIds() if self.settings.value("saveSelectionInProject"): self.layer.setCustomProperty("teamqgisSelection", repr(self.subset)) for fid in self.subset: self.listCombo.addItem("%u" % fid) self.setRubber(self.getCurrentItem()) self.updateValueComboBoxes() def layerChanged(self): self.applyChangesButton.setEnabled(True) def editingStarted(self): for valueComboBox in self.valueComboBoxes: valueComboBox.setEnabled(True) self.translateRightButton.setEnabled(True) self.translateLeftButton.setEnabled(True) self.translateUpButton.setEnabled(True) self.translateDownButton.setEnabled(True) self.editFormButton.setDown(True) def editingStopped(self): self.applyChangesButton.setEnabled(False) for valueComboBox in self.valueComboBoxes: valueComboBox.setEnabled(False) self.translateRightButton.setEnabled(False) self.translateLeftButton.setEnabled(False) self.translateUpButton.setEnabled(False) self.translateDownButton.setEnabled(False) self.editFormButton.setDown(False) def cleanBrowserFields(self): self.currentPosLabel.setText('0/0') self.listCombo.clear() def panScaleToItem(self, feature): if self.panCheck.isChecked() is False: return featBobo = feature.geometry().boundingBox() # if scaling and bobo has width and height (i.e. not a point) if self.scaleCheck.isChecked() and featBobo.width() != 0 and featBobo.height() != 0: featBobo.scale(self.settings.value("scale")) ul = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMaximum())) ur = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMaximum())) ll = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMinimum())) lr = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMinimum())) x = (ul.x(), ur.x(), ll.x(), lr.x()) y = (ul.y(), ur.y(), ll.y(), lr.y()) x0 = min(x) y0 = min(y) x1 = max(x) y1 = max(y) else: panTo = self.renderer.layerToMapCoordinates(self.layer, featBobo.center()) mapBobo = self.iface.mapCanvas().extent() xshift = panTo.x() - mapBobo.center().x() yshift = panTo.y() - mapBobo.center().y() x0 = mapBobo.xMinimum() + xshift y0 = mapBobo.yMinimum() + yshift x1 = mapBobo.xMaximum() + xshift y1 = mapBobo.yMaximum() + yshift self.iface.mapCanvas().setExtent(QgsRectangle(x0, y0, x1, y1)) self.iface.mapCanvas().refresh() def getCurrentItem(self): i = self.listCombo.currentIndex() if i == -1: return None f = QgsFeature() if self.layer.getFeatures(QgsFeatureRequest().setFilterFid(self.subset[i])).nextFeature(f): return f else: raise NameError("feature not found") def doAction(self, i): f = self.getCurrentItem() self.actionButton.setDefaultAction(self.actionButton.actions()[i]) self.layer.setCustomProperty("teamqgisPreferedAction", self.attrAction[i].name()) self.attrAction.doActionFeature(i, f) def doTranslate(self, trans): # Based on the "doaffine" function in the qgsAffine plugin if (self.layer.geometryType() == 2): start=1 else: start=0 if (not self.layer.isEditable()): self.iface.messageBar().pushMessage("Layer not in edit mode", 'Select a vector layer and choose "Toggle Editing"', level=QgsMessageBar.WARNING) else: feature = self.getCurrentItem() result = feature.geometry() i = start vertex = result.vertexAt(i) fid = feature.id() while (vertex != QgsPoint(0, 0)): newx = vertex.x() + trans[0] * float(self.settings.value("xres")) newy = vertex.y() + trans[1] * float(self.settings.value("yres")) result.moveVertex(newx, newy, i) i += 1 vertex = result.vertexAt(i) self.layer.changeGeometry(fid, result) self.iface.mapCanvas().refresh() self.rubber.reset() self.setRubber(feature) def changeAttribute(self, i, fieldNameComboBox, fieldValueComboBox): fieldValueComboBox.setCurrentIndex(i) feature = self.getCurrentItem() attr_index = self.layer.dataProvider().fieldNameMap()[fieldNameComboBox.currentText()] self.layer.changeAttributeValue(feature.id(), attr_index, fieldValueComboBox.currentText()) self.iface.mapCanvas().refresh() self.updateValueComboBoxes() def nameComboBox_activated(self, i, nameComboBox): self.layer.setCustomProperty('teamqgis' + nameComboBox.objectName(), nameComboBox.currentText()) @pyqtSlot(name="on_previousButton_clicked") def previousFeature(self): i = self.listCombo.currentIndex() n = max(0, i-1) self.listCombo.setCurrentIndex(n) self.saveCurrentFeature(n) @pyqtSlot(name="on_nextButton_clicked") def nextFeature(self): self.warned_attr_values = [] # Reset attr warnings i = self.listCombo.currentIndex() c = self.listCombo.count() n = min(i+1, c-1) self.listCombo.setCurrentIndex(n) self.saveCurrentFeature(n) @pyqtSlot(int, name="on_listCombo_activated") def saveCurrentFeature(self, i): if self.settings.value("saveSelectionInProject"): self.layer.setCustomProperty("teamqgisCurrentItem", i) @pyqtSlot(int, name="on_fieldOneNameComboBox_activated") def fieldOneNameComboBox_activated(self, i): self.nameComboBox_activated(i, self.fieldOneNameComboBox) @pyqtSlot(int, name="on_fieldTwoNameComboBox_activated") def fieldTwoNameComboBox_activated(self, i): self.nameComboBox_activated(i, self.fieldTwoNameComboBox) @pyqtSlot(int, name="on_fieldThreeNameComboBox_activated") def fieldThreeNameComboBox_activated(self, i): self.nameComboBox_activated(i, self.fieldThreeNameComboBox) @pyqtSlot(int, name="on_listCombo_currentIndexChanged") def on_listCombo_currentIndexChanged(self, i): feature = self.getCurrentItem() if feature is None: return self.rubber.reset() if self.listCombo.count() > 1: self.setRubber(feature) self.updateValueComboBoxes() # scale to feature self.panScaleToItem(feature) # Update browser self.currentPosLabel.setText("%u/%u" % (i+1, len(self.subset))) # emit signal self.layer.emit(SIGNAL("browserCurrentItem(long)"), feature.id()) @pyqtSlot(int, name="on_panCheck_stateChanged") def on_panCheck_stateChanged(self, i): if self.panCheck.isChecked(): self.scaleCheck.setEnabled(True) feature = self.getCurrentItem() if feature is None: return self.panScaleToItem(feature) else: self.scaleCheck.setEnabled(False) @pyqtSlot(int, name="on_scaleCheck_stateChanged") def on_scaleCheck_stateChanged(self, i): if self.scaleCheck.isChecked(): feature = self.getCurrentItem() if feature is None: return self.panScaleToItem(feature) @pyqtSlot(name="on_editFormButton_clicked") def openFeatureForm(self): if (self.layer.isEditable()): self.layer.commitChanges() else: self.layer.startEditing() @pyqtSlot(name="on_translateRightButton_clicked") def doTranslateRight(self): self.doTranslate((1, 0)) @pyqtSlot(name="on_translateLeftButton_clicked") def doTranslateLeft(self): self.doTranslate((-1, 0)) @pyqtSlot(name="on_translateUpButton_clicked") def doTranslateUp(self): self.doTranslate((0, 1)) @pyqtSlot(name="on_translateDownButton_clicked") def doTranslateDown(self): self.doTranslate((0, -1)) @pyqtSlot(name="on_applyChangesButton_clicked") def applyChanges(self): self.layer.commitChanges() self.layer.startEditing() self.layer.updateExtents() self.iface.mapCanvas().refresh() @pyqtSlot(int, name="on_fieldOneValueComboBox_activated") def on_fieldOneValueComboBox_activated(self, i): self.changeAttribute(i, self.fieldOneNameComboBox, self.fieldOneValueComboBox) @pyqtSlot(int, name="on_fieldTwoValueComboBox_activated") def on_fieldTwoValueComboBox_activated(self, i): self.changeAttribute(i, self.fieldTwoNameComboBox, self.fieldTwoValueComboBox) @pyqtSlot(int, name="on_fieldThreeValueComboBox_activated") def on_fieldThreeValueComboBox_activated(self, i): self.changeAttribute(i, self.fieldThreeNameComboBox, self.fieldThreeValueComboBox)
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 SelectMapTool(QgsMapToolEmitPoint): features_selected_signal = pyqtSignal() selected_feature_ids = list() def __init__(self, canvas, layer, multi=True): self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self._multi = multi self._layer = layer # Cursor cursor = QCursor() cursor.setShape(Qt.CrossCursor) self.setCursor(cursor) # RubberBand Style self.rubberBand = QgsRubberBand(self.canvas, True) self.rubberBand.setBrushStyle(Qt.Dense4Pattern) self.rubberBand.setColor(QColor(255, 181, 92)) self.rubberBand.setStrokeColor(QColor(232, 137, 72)) self.rubberBand.setWidth(0.2) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(True) def canvasPressEvent(self, e): if e.button() & Qt.LeftButton: self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True def canvasReleaseEvent(self, e): if e.button() & Qt.LeftButton: self.isEmittingPoint = False self.show_rubber_band() if self._multi and (e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.ShiftModifier): if self.rubberBand.asGeometry().type( ) == QgsWkbTypes.PolygonGeometry: self.reset() return else: id_features_intersect = self.ids_features_intersect() # if Control or Shift are selected, keeps the previous selection for id_feature_intersect in id_features_intersect: # toggle selected feature if id_feature_intersect not in self.selected_feature_ids: self.selected_feature_ids.append(id_feature_intersect) else: self.selected_feature_ids.remove(id_feature_intersect) else: id_features_intersect = self.ids_features_intersect() # Clear selected features if Control or Shift are not selected self.selected_feature_ids = id_features_intersect self.select_features() elif e.button() & Qt.RightButton: self.features_selected_signal.emit() self.reset() def canvasDoubleClickEvent(self, e): self.canvasPressEvent(e) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.show_rubber_band() def show_rubber_band(self): self.create_rubber_band() if self.rubberBand: self.rubberBand.show() def create_rubber_band(self): self.rubberBand.reset(True) if self.startPoint is None and self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): self.rubberBand.reset(QgsWkbTypes.PointGeometry) self.rubberBand.addPoint(self.startPoint, True) # true to update canvas else: self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) point1 = QgsPointXY(self.startPoint.x(), self.startPoint.y()) point2 = QgsPointXY(self.startPoint.x(), self.endPoint.y()) point3 = QgsPointXY(self.endPoint.x(), self.endPoint.y()) point4 = QgsPointXY(self.endPoint.x(), self.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 # Approximate polygon to point when it's too small related to the scale if (self.rubberBand.asGeometry().area() / self.canvas.extent().area()) * 1000000 < 50: self.rubberBand.reset(QgsWkbTypes.PointGeometry) self.rubberBand.addPoint(self.startPoint, True) # true to update canvas def ids_features_intersect(self): index = QgsSpatialIndex(self._layer) bbox = self.rubberBand.asGeometry().boundingBox() candidate_features = self._layer.getFeatures(index.intersects(bbox)) geom = self.rubberBand.asGeometry() centroid = geom if not self._multi and geom.type() == QgsWkbTypes.PolygonGeometry: centroid = geom.centroid() features_selected = [] distances_features_selected = dict() for candidate_feature in candidate_features: if candidate_feature.geometry().intersects(geom): features_selected.append(candidate_feature.id()) # Calculate the distance to the centroid distances_features_selected[candidate_feature.id( )] = candidate_feature.geometry().distance(centroid) if not self._multi and geom.type() == QgsWkbTypes.PolygonGeometry: if features_selected: # Get the key corresponding to the minimum value within a dictionary features_selected = [ min(distances_features_selected, key=distances_features_selected.get) ] return features_selected def select_features(self): # show features selected self._layer.selectByIds(self.selected_feature_ids) self.reset() def deactivate(self): QgsMapTool.deactivate(self) self.deactivated.emit()
class QuadInstanceItemWidget(QFrame): quadSelected = pyqtSignal() def __init__(self, quad): QWidget.__init__(self) self.setMouseTracking(True) self.quad = quad self.nameLabel = QLabel( f'<b>{quad[ID]}</b><br><span style="color:grey;">' f'{quad[PERCENT_COVERED]} % covered</span>') self.iconLabel = QLabel() pixmap = QPixmap(PLACEHOLDER_THUMB, 'SVG') thumb = pixmap.scaled(48, 48, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self.iconLabel.setPixmap(thumb) self.checkBox = QCheckBox("") self.checkBox.stateChanged.connect(self.check_box_state_changed) layout = QHBoxLayout() layout.setMargin(0) layout.addWidget(self.checkBox) vlayout = QVBoxLayout() vlayout.setMargin(0) vlayout.addWidget(self.iconLabel) self.iconWidget = QWidget() self.iconWidget.setFixedSize(48, 48) self.iconWidget.setLayout(vlayout) layout.addWidget(self.iconWidget) layout.addWidget(self.nameLabel) layout.addStretch() self.setLayout(layout) download_thumbnail(quad[LINKS][THUMBNAIL], self) self.footprint = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.footprint.setFillColor(QUADS_AOI_COLOR) self.footprint.setStrokeColor(QUADS_AOI_COLOR) self.footprint.setWidth(2) self.footprintfill = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.footprintfill.setFillColor(QUADS_AOI_BODY_COLOR) self.footprintfill.setWidth(0) self.update_footprint_brush() self.hide_solid_interior() self.show_footprint() self.setStyleSheet( "QuadInstanceItemWidget{border: 2px solid transparent;}") def set_thumbnail(self, img): pixmap = QPixmap(img) thumb = pixmap.scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.iconLabel.setPixmap(thumb) self.iconLabel.setStyleSheet("") def check_box_state_changed(self): self.update_footprint_brush() self.quadSelected.emit() def show_footprint(self): coords = self.quad[BBOX] extent = qgsrectangle_for_canvas_from_4326_bbox_coords(coords) self.geom = QgsGeometry.fromRect(extent) self.footprint.setToGeometry(self.geom) self.footprintfill.setToGeometry(self.geom) def hide_footprint(self): self.footprint.reset(QgsWkbTypes.PolygonGeometry) self.footprintfill.reset(QgsWkbTypes.PolygonGeometry) def show_solid_interior(self): self.footprintfill.setBrushStyle(Qt.SolidPattern) self.footprintfill.updateCanvas() def hide_solid_interior(self): self.footprintfill.setBrushStyle(Qt.NoBrush) self.footprintfill.updateCanvas() def update_footprint_brush(self): self.footprint.setBrushStyle( Qt.BDiagPattern if self.checkBox.isChecked() else Qt.NoBrush) self.footprint.updateCanvas() def remove_footprint(self): iface.mapCanvas().scene().removeItem(self.footprint) iface.mapCanvas().scene().removeItem(self.footprintfill) def isSelected(self): return self.checkBox.isChecked() def setChecked(self, checked, emit=True): if not emit: self.checkBox.blockSignals(True) self.checkBox.setChecked(checked) if not emit: self.update_footprint_brush() self.checkBox.blockSignals(False) def enterEvent(self, event): self.setStyleSheet( "QuadInstanceItemWidget{border: 2px solid rgb(157, 165, 0);}") self.show_solid_interior() def leaveEvent(self, event): self.setStyleSheet( "QuadInstanceItemWidget{border: 2px solid transparent;}") self.hide_solid_interior()
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 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 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 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 wincan2qgep(QObject): name = u"&Wincan 2 QGEP" actions = None def __init__(self, iface): QObject.__init__(self) self.iface = iface self.actions = {} self.settings = MySettings() self.dlg = None # translation environment self.plugin_dir = os.path.dirname(__file__) locale = QSettings().value("locale/userLocale")[0:2] localePath = os.path.join(self.plugin_dir, 'i18n', 'wincan2qgep_{0}.qm'.format(locale)) if os.path.exists(localePath): self.translator = QTranslator() self.translator.load(localePath) QCoreApplication.installTranslator(self.translator) def initGui(self): self.actions['openInspection'] = QAction( QIcon(":/plugins/wincan2qgep/icons/wincan_logo.png"), self.tr(u"Ouvrir une inspection"), self.iface.mainWindow()) self.actions['openInspection'].triggered.connect(self.openInspection) self.iface.addPluginToMenu(self.name, self.actions['openInspection']) self.iface.addToolBarIcon(self.actions['openInspection']) self.actions['showSettings'] = QAction( QIcon(":/plugins/wincan2qgep/icons/settings.svg"), self.tr(u"&Settings"), self.iface.mainWindow()) self.actions['showSettings'].triggered.connect(self.showSettings) self.iface.addPluginToMenu(self.name, self.actions['showSettings']) self.actions['help'] = QAction( QIcon(":/plugins/wincan2qgep/icons/help.svg"), self.tr("Help"), self.iface.mainWindow()) self.actions['help'].triggered.connect(lambda: QDesktopServices().openUrl(QUrl("http://3nids.github.io/wincan2qgep"))) self.iface.addPluginToMenu(self.name, self.actions['help']) self.rubber = QgsRubberBand(self.iface.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) def unload(self): """ Unload plugin """ for action in self.actions.itervalues(): self.iface.removePluginMenu(self.name, action) self.iface.removeToolBarIcon(action) if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber if self.dlg: self.dlg.close() @pyqtSlot(str, QgsMessageBar.MessageLevel) def displayMessage(self, message, level): self.iface.messageBar().pushMessage("Wincan 2 QGEP", message, level) def showSettings(self): if ConfigurationDialog().exec_(): self._reloadFinders() def openInspection(self): xmlPath = self.settings.value('xmlPath') if xmlPath == '': xmlPath = QgsProject.instance().homePath() filepath = QFileDialog.getOpenFileName(None, "Open WIncan inspection data", xmlPath, "Wincan file (*.xml)") #filepath = '/var/run/user/1000/gvfs/smb-share:server=s4laveyre.sige.ch,share=inspection_tv/SIGE_2014/Rapport 2014/SIGE 5004B 14/XML/Project.xml' if filepath: self.settings.set_value('xmlPath', os.path.dirname(os.path.realpath(filepath))) data = ImportData(filepath).data self.dlg = DataBrowserDialog(self.iface, data) self.dlg.show()
class ConversionDialog(QDockWidget, FORM_CLASS): def __init__(self, iface, parent): super(ConversionDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.canvas = iface.mapCanvas() self.savedMapTool = None # 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) self.tf = tzf_instance.getTZF() self.timeEdit.setDisplayFormat("HH:mm:ss") self.clipboard = QApplication.clipboard() font = self.sunTextEdit.font() font.setFamily("Courier New") self.sunTextEdit.setFont(font) # Set up a connection with the coordinate capture tool self.captureCoordinate = CaptureCoordinate(self.canvas) self.captureCoordinate.capturePoint.connect(self.capturedPoint) self.captureCoordinate.captureStopped.connect(self.stopCapture) self.coordCaptureButton.clicked.connect(self.startCapture) self.timezoneComboBox.view().setVerticalScrollBarPolicy( Qt.ScrollBarAsNeeded) self.timezoneComboBox.addItems(all_timezones) self.timezoneComboBox.currentIndexChanged.connect( self.timezone_changed) self.dateEdit.setCalendarPopup(True) self.coordCaptureButton.setIcon( QIcon(os.path.dirname(__file__) + "/images/coordCapture.svg")) self.currentDateTimeButton.setIcon( QIcon(os.path.dirname(__file__) + "/images/CurrentTime.png")) icon = QIcon( ':/images/themes/default/algorithms/mAlgorithmCheckGeometry.svg') self.epochCommitButton.setIcon(icon) self.epochmsCommitButton.setIcon(icon) self.julianCommitButton.setIcon(icon) self.iso8601CommitButton.setIcon(icon) self.iso8601_2_CommitButton.setIcon(icon) self.coordCommitButton.setIcon(icon) icon = QIcon(':/images/themes/default/mActionEditCopy.svg') self.epochCopyButton.setIcon(icon) self.epochmsCopyButton.setIcon(icon) self.julianCopyButton.setIcon(icon) self.iso8601CopyButton.setIcon(icon) self.iso8601_2_CopyButton.setIcon(icon) self.dateDifferenceCopyButton.setIcon(icon) self.coordCopyButton.setIcon(icon) self.timezoneCopyButton.setIcon(icon) self.sunCopyButton.setIcon(icon) self.sunAzimuthCopyButton.setIcon(icon) self.sunElevationCopyButton.setIcon(icon) self.coordLineEdit.returnPressed.connect( self.on_coordCommitButton_pressed) self.epochLineEdit.returnPressed.connect( self.on_epochCommitButton_pressed) self.epochmsLineEdit.returnPressed.connect( self.on_epochmsCommitButton_pressed) self.julienLineEdit.returnPressed.connect( self.on_julianCommitButton_pressed) self.iso8601LineEdit.returnPressed.connect( self.on_iso8601CommitButton_pressed) self.iso8601_2_LineEdit.returnPressed.connect( self.on_iso8601_2_CommitButton_pressed) def showEvent(self, e): self.initSystemTime() self.updateDateTime() def closeEvent(self, e): if self.savedMapTool: self.canvas.setMapTool(self.savedMapTool) self.savedMapTool = None self.rubber.reset() def initSystemTime(self): self.tz = tzlocal() dt = datetime.now(self.tz) try: name = dt.tzinfo.zone except Exception: name = dt.tzname() if name in win_tz_map: name = win_tz_map[name] id = self.timezoneComboBox.findText(name, Qt.MatchExactly) if id == -1: offset = int(dt.tzinfo.utcoffset(dt).total_seconds() / 3600.0) name = 'Etc/GMT{:+d}'.format(-offset) self.dt_utc = datetime.now(pytz.utc) self.tz = timezone(name) def getLocalDateTime(self): # print('getLocalDateTime') dt = self.dt_utc.astimezone(self.tz) return (dt) def startCapture(self): # print('startCapture') if self.coordCaptureButton.isChecked(): self.savedMapTool = self.canvas.mapTool() self.canvas.setMapTool(self.captureCoordinate) else: if self.savedMapTool: self.canvas.setMapTool(self.savedMapTool) self.savedMapTool = None @pyqtSlot(QgsPointXY) def capturedPoint(self, pt): # print('capturedPoint') if self.isVisible() and self.coordCaptureButton.isChecked(): coord = '{}, {}'.format(pt.y(), pt.x()) self.coordLineEdit.setText(coord) if self.setCoordinateTimezone(pt.y(), pt.x()): self.updateDateTime() @pyqtSlot() def stopCapture(self): # print('stopCapture') self.coordCaptureButton.setChecked(False) self.rubber.reset() def setCoordinateTimezone(self, lat, lon): tzname = self.tf.timezone_at(lng=lon, lat=lat) if tzname != None: polygon = self.tf.get_geometry(tz_name=tzname) qgs_poly = tzf_to_qgis_polygon(polygon) canvas_crs = self.canvas.mapSettings().destinationCrs() if epsg4326 != canvas_crs: to_canvas_crs = QgsCoordinateTransform(epsg4326, canvas_crs, QgsProject.instance()) qgs_poly.transform(to_canvas_crs) self.rubber.reset() self.rubber.addGeometry(qgs_poly, None) self.rubber.show() # print('tzname {}'.format(tzname)) if tzname: self.tz = timezone(tzname) dt = self.getLocalDateTime() dt = dt.replace(tzinfo=None) dt = self.tz.localize(dt) self.dt_utc = dt.astimezone(pytz.utc) return (True) return (False) def updateDateTime(self, id=Update.ALL): # print('updateDateTime') if self.hourModeCheckBox.isChecked(): self.timeEdit.setDisplayFormat("HH:mm:ss") else: self.timeEdit.setDisplayFormat("hh:mm:ss AP") dt = self.getLocalDateTime() # offset = int(dt.tzinfo.utcoffset(dt).total_seconds()/3600.0) offset = dt.strftime('%z') if id != Update.DATE: self.dateEdit.blockSignals(True) self.dateEdit.setDate(QDate(dt.year, dt.month, dt.day)) self.dateEdit.blockSignals(False) if id != Update.TIME: self.timeEdit.blockSignals(True) self.timeEdit.setTime( QTime(dt.hour, dt.minute, dt.second, int(dt.microsecond / 1000))) self.timeEdit.blockSignals(False) if id != Update.EPOCH: try: sec = int( (self.dt_utc - datetime.fromtimestamp(0, pytz.utc)).total_seconds()) except Exception: sec = 'Undefined' self.epochLineEdit.setText('{}'.format(sec)) if id != Update.EPOCHMS: try: msec = int( (self.dt_utc - datetime.fromtimestamp(0, pytz.utc)).total_seconds() * 1000.0) except Exception: msec = 'Undefined' self.epochmsLineEdit.setText('{}'.format(msec)) if id != Update.TIME_ZONE: # This should always be true because we have forced the timezone to adhear to the list name = dt.tzinfo.zone id = self.timezoneComboBox.findText(name, Qt.MatchExactly) self.timezoneComboBox.blockSignals(True) self.timezoneComboBox.setCurrentIndex(id) self.timezoneComboBox.blockSignals(False) if id != Update.JULIAN: (val1, val2) = gcal2jd(dt.year, dt.month, dt.day) self.julienLineEdit.setText('{}'.format(val1 + val2)) if id != Update.UTC: if dt.microsecond == 0: self.iso8601LineEdit.setText( self.dt_utc.strftime('%Y-%m-%dT%H:%M:%SZ')) else: self.iso8601LineEdit.setText( self.dt_utc.strftime('%Y-%m-%dT%H:%M:%S.%fZ')) tinfo = dt.timetuple() self.dayOfWeekLineEdit.setText(DOW[tinfo.tm_wday]) self.doyLineEdit.setText('{}'.format(tinfo.tm_yday)) # self.timezoneOffsetLineEdit.setText('{:+d}'.format(offset)) self.timezoneOffsetLineEdit.setText(offset) self.updateTimeDelta() self.updateSunMoon() self.updateReverseGeo() def updateSunMoon(self): # print('updateSunMoon') try: (lat, lon) = parseDMSString(self.coordLineEdit.text().strip()) except Exception: self.clearSun() return locl = astral.LocationInfo('', '', '', lat, lon) dt = self.getLocalDateTime() try: if self.displaySunUtcCheckBox.isChecked(): s = sun(locl.observer, date=dt, tzinfo=pytz.utc) else: s = sun(locl.observer, date=dt, tzinfo=dt.tzinfo) except Exception: self.clearSun() return msg = [] if self.displaySunUtcCheckBox.isChecked(): fmt = '%Y-%m-%dT%H:%M:%SZ' else: fmt = '%Y-%m-%dT%H:%M:%S%z' for k, v in s.items(): msg.append('{:>7}, {}'.format(k, v.strftime(fmt))) sunstr = '\n'.join(msg) self.sunTextEdit.clear() self.sunTextEdit.insertPlainText(sunstr) azimuth = astral.sun.azimuth(locl.observer, dt) elev = astral.sun.elevation(locl.observer, dt) self.sunAzimuthLineEdit.setText('{:.8f}'.format(azimuth)) self.sunElevationLineEdit.setText('{:.8f}'.format(elev)) def updateReverseGeo(self): try: (lat, lon) = parseDMSString(self.coordLineEdit.text().strip()) except Exception: self.clearReverseGeo() return results = rg.search(((lat, lon)), mode=1) self.clearReverseGeo() str = '{}, {}, {}'.format(results[0]['cc'], results[0]['admin1'], results[0]['admin2']) self.reverseGeoLinedit.setText(str) # Get the azimuth and distance to the nearest know location l = geod.Inverse(lat, lon, float(results[0]['lat']), float(results[0]['lon'])) heading = l['azi1'] dist = l['s12'] / 1000.0 # put it in kilometers str = '{} {:.1f}° / {:.1f} km'.format(results[0]['name'], heading, dist) self.headingDistLineEdit.setText(str) def clearReverseGeo(self): self.reverseGeoLinedit.setText('') self.headingDistLineEdit.setText('') def clearSun(self): self.sunTextEdit.clear() self.sunAzimuthLineEdit.setText('') self.sunElevationLineEdit.setText('') def timezone_changed(self, index): tz_name = str(self.timezoneComboBox.itemText(index)) self.tz = timezone(tz_name) dt = self.getLocalDateTime() dt = dt.replace(tzinfo=None) dt = self.tz.localize(dt) self.dt_utc = dt.astimezone(pytz.utc) self.updateDateTime(Update.TIME_ZONE) def on_currentDateTimeButton_pressed(self): self.initSystemTime() self.coordLineEdit.setText('') self.updateDateTime() def on_hourModeCheckBox_stateChanged(self, state): if self.hourModeCheckBox.isChecked(): self.timeEdit.setDisplayFormat("HH:mm:ss") else: self.timeEdit.setDisplayFormat("hh:mm:ss AP") def on_displaySunUtcCheckBox_stateChanged(self, state): self.updateSunMoon() def on_dateEdit_dateChanged(self, date): olddt = self.getLocalDateTime() dt = datetime(date.year(), date.month(), date.day(), olddt.hour, olddt.minute, olddt.second, olddt.microsecond) dt = self.tz.localize(dt) self.dt_utc = dt.astimezone(pytz.utc) self.updateDateTime(Update.DATE) def on_timeEdit_timeChanged(self, time): # print('on_timeEdit_timeChanged') olddt = self.getLocalDateTime() dt = datetime(olddt.year, olddt.month, olddt.day, time.hour(), time.minute(), time.second(), time.msec() * 1000) dt = self.tz.localize(dt) self.dt_utc = dt.astimezone(pytz.utc) self.updateDateTime(Update.TIME) def on_coordCommitButton_pressed(self): # print('coordCommitButton Pressed') try: coord = self.coordLineEdit.text().strip() if not coord: self.clearSun() self.clearReverseGeo() return (lat, lon) = parseDMSString(coord) self.setCoordinateTimezone(lat, lon) self.updateDateTime() except Exception: self.iface.messageBar().pushMessage( "", "Invalid 'latitude, longitude'", level=Qgis.Warning, duration=2) return def on_epochCommitButton_pressed(self): # print('epochCommitButton Pressed') try: epoch = long(self.epochLineEdit.text().strip()) except Exception: return self.dt_utc = datetime.fromtimestamp(epoch, pytz.utc) self.updateDateTime(Update.EPOCH) def on_epochmsCommitButton_pressed(self): # print('epochmsCommitButton Pressed') try: epoch = long(self.epochmsLineEdit.text().strip()) / 1000.0 except Exception: return self.dt_utc = datetime.fromtimestamp(epoch, pytz.utc) self.updateDateTime(Update.EPOCHMS) def on_julianCommitButton_pressed(self): # print('on_julianCommitButton_pressed Pressed') try: julian = float(self.julienLineEdit.text().strip()) - MJD_0 date = jd2gcal(MJD_0, julian) except Exception: self.iface.messageBar().pushMessage("", "Invalid julian date", level=Qgis.Warning, duration=2) return olddt = self.getLocalDateTime() dt = datetime(date[0], date[1], date[2], olddt.hour, olddt.minute, olddt.second, olddt.microsecond) dt = self.tz.localize(dt) self.dt_utc = dt.astimezone(pytz.utc) self.updateDateTime(Update.JULIAN) def on_iso8601CommitButton_pressed(self): str = self.iso8601LineEdit.text().strip() if not str: return try: dt = dateutil.parser.parse(str, default=datetime(MINYEAR, 1, 1, hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.utc)) except Exception: self.iface.messageBar().pushMessage( "", "Invalid ISO8601 date and time", level=Qgis.Warning, duration=2) return self.dt_utc = dt.astimezone(pytz.utc) self.updateDateTime(Update.UTC) def on_iso8601_2_CommitButton_pressed(self): self.updateTimeDelta() def updateTimeDelta(self): str = self.iso8601_2_LineEdit.text().strip() if not str: return try: dt_delta = dateutil.parser.parse(str, default=datetime(MINYEAR, 1, 1, hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.utc)) except Exception: self.iface.messageBar().pushMessage( "", "Invalid ISO8601 date and time", level=Qgis.Warning, duration=2) return diff = relativedelta(dt_delta, self.dt_utc) msg = '{}y {}m {}d {}h {}m {}s {}uS'.format(diff.years, diff.months, diff.days, diff.hours, diff.minutes, diff.seconds, diff.microseconds) self.dtDeltaLineEdit.setText(msg) def on_timezoneCopyButton_pressed(self): s = str(self.timezoneComboBox.currentText()) self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_coordCopyButton_pressed(self): s = self.coordLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_epochCopyButton_pressed(self): s = self.epochLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_epochmsCopyButton_pressed(self): s = self.epochmsLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_julianCopyButton_pressed(self): s = self.julienLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_iso8601CopyButton_pressed(self): s = self.iso8601LineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_iso8601_2_CopyButton_pressed(self): s = self.iso8601_2_LineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_dateDifferenceCopyButton_pressed(self): s = self.dtDeltaLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_sunCopyButton_pressed(self): s = self.sunTextEdit.toPlainText().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "Sun information copied to the clipboard", level=Qgis.Info, duration=3) def on_sunAzimuthCopyButton_pressed(self): s = self.sunAzimuthLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3) def on_sunElevationCopyButton_pressed(self): s = self.sunElevationLineEdit.text().strip() self.clipboard.setText(s) self.iface.messageBar().pushMessage( "", "{} copied to the clipboard".format(s), level=Qgis.Info, duration=3)
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 teamqgisDock(QDockWidget, Ui_teamqgis): dockRemoved = pyqtSignal(str) def __init__(self, iface, layer, currentFeature): self.iface = iface self.layer = layer self.proj = QgsProject.instance() self.renderer = self.iface.mapCanvas().mapRenderer() self.settings = MySettings() QDockWidget.__init__(self) self.setupUi(self) # Track attr warnings so they are not repeated for multiple items self.warned_attr_values = [] self.setWindowTitle("teamqgis: %s" % layer.name()) if layer.hasGeometryType() is False: self.panCheck.setChecked(False) self.panCheck.setEnabled(False) self.scaleCheck.setChecked(False) self.scaleCheck.setEnabled(False) self.previousButton.setArrowType(Qt.LeftArrow) self.nextButton.setArrowType(Qt.RightArrow) icon = QIcon(":/plugins/teamqgis/icons/openform.svg") self.editFormButton.setIcon(icon) # actions icon = QIcon(":/plugins/teamqgis/icons/action.svg") self.actionButton.setIcon(icon) self.attrAction = layer.actions() actions = [self.attrAction[i] for i in range(self.attrAction.size())] preferredAction = layer.customProperty("teamqgisPreferedAction", "") if preferredAction not in actions: dfltAction = self.attrAction.defaultAction() if dfltAction > len(actions): preferredAction = self.attrAction[dfltAction].name() preferredActionFound = False for i, action in enumerate(actions): qAction = QAction(QIcon(":/plugins/teamqgis/icons/action.svg"), action.name(), self) qAction.triggered.connect(lambda: self.doAction(i)) self.actionButton.addAction(qAction) if action.name() == preferredAction: self.actionButton.setDefaultAction(qAction) preferredActionFound = True if len(actions) == 0: self.actionButton.setEnabled(False) elif not preferredActionFound: self.actionButton.setDefaultAction(self.actionButton.actions()[0]) self.nameComboBoxes = [ self.fieldOneNameComboBox, self.fieldTwoNameComboBox, self.fieldThreeNameComboBox ] self.valueComboBoxes = [ self.fieldOneValueComboBox, self.fieldTwoValueComboBox, self.fieldThreeValueComboBox ] self.updateNameComboBoxes() # Restore saved nameComboBox current indices if they exist for nameComboBox in self.nameComboBoxes: fieldName = self.layer.customProperty("teamqgis" + nameComboBox.objectName()) if fieldName != None: nameComboBox.setCurrentIndex(nameComboBox.findText(fieldName)) self.rubber = QgsRubberBand(self.iface.mapCanvas()) self.selectionChanged() if currentFeature == self.listCombo.currentIndex(): self.on_listCombo_currentIndexChanged(currentFeature) else: self.listCombo.setCurrentIndex(currentFeature) self.layer.layerDeleted.connect(self.close) self.layer.selectionChanged.connect(self.selectionChanged) self.layer.layerModified.connect(self.layerChanged) self.layer.editingStopped.connect(self.editingStopped) self.layer.editingStarted.connect(self.editingStarted) QObject.connect(self.proj, SIGNAL("allowedClassesChanged()"), self.updateValueComboBoxes) def updateNameComboBoxes(self): fieldNameMap = self.layer.dataProvider().fieldNameMap() allFields = fieldNameMap.keys() if 'ID' in allFields: allFields.remove('ID') if 'FID' in allFields: allFields.remove('FID') for nameComboBox in self.nameComboBoxes: nameComboBox.clear() nameComboBox.addItems(allFields) def updateValueComboBoxes(self): feature = self.getCurrentItem() for (valueComboBox, nameComboBox) in zip(self.valueComboBoxes, self.nameComboBoxes): valueComboBox.clear() allowedClasses, hasAllowedClasses = self.proj.readListEntry( "teamqgis", "allowedClasses") if hasAllowedClasses: valueComboBox.addItems(allowedClasses) attr_value = str(feature[nameComboBox.currentText()]) if (allowedClasses == None) or (attr_value not in allowedClasses) and ( attr_value not in self.warned_attr_values): self.iface.messageBar().pushMessage( "Class name not in allowed class list", 'Assign an allowed class or add "%s" to allowed class list' % attr_value, level=QgsMessageBar.WARNING, duration=3) self.warned_attr_values.append(attr_value) valueComboBox.addItem(attr_value) valueComboBox.setCurrentIndex(valueComboBox.findText(attr_value)) def setRubber(self, feature): self.rubber.setColor(self.settings.value("rubberColor")) self.rubber.setWidth(self.settings.value("rubberWidth")) ##self.rubber.setLineStyle(Qt.DotLine) self.rubber.setBrushStyle(Qt.NoBrush) self.rubber.setToGeometry(feature.geometry(), self.layer) def closeEvent(self, e): self.rubber.reset() self.layer.layerDeleted.disconnect(self.close) self.layer.selectionChanged.disconnect(self.selectionChanged) self.layer.layerModified.disconnect(self.layerChanged) self.layer.editingStopped.disconnect(self.editingStopped) self.layer.editingStarted.disconnect(self.editingStarted) if self.settings.value("saveSelectionInProject"): self.layer.setCustomProperty("teamqgisSelection", repr([])) self.dockRemoved.emit(self.layer.id()) def selectionChanged(self): self.cleanBrowserFields() self.rubber.reset() nItems = self.layer.selectedFeatureCount() if nItems < 1: self.close() self.layer.emit(SIGNAL("browserNoItem()")) return self.browseFrame.setEnabled(True) self.subset = self.layer.selectedFeaturesIds() if self.settings.value("saveSelectionInProject"): self.layer.setCustomProperty("teamqgisSelection", repr(self.subset)) for fid in self.subset: self.listCombo.addItem("%u" % fid) self.setRubber(self.getCurrentItem()) self.updateValueComboBoxes() def layerChanged(self): self.applyChangesButton.setEnabled(True) def editingStarted(self): for valueComboBox in self.valueComboBoxes: valueComboBox.setEnabled(True) self.translateRightButton.setEnabled(True) self.translateLeftButton.setEnabled(True) self.translateUpButton.setEnabled(True) self.translateDownButton.setEnabled(True) self.editFormButton.setDown(True) def editingStopped(self): self.applyChangesButton.setEnabled(False) for valueComboBox in self.valueComboBoxes: valueComboBox.setEnabled(False) self.translateRightButton.setEnabled(False) self.translateLeftButton.setEnabled(False) self.translateUpButton.setEnabled(False) self.translateDownButton.setEnabled(False) self.editFormButton.setDown(False) def cleanBrowserFields(self): self.currentPosLabel.setText('0/0') self.listCombo.clear() def panScaleToItem(self, feature): if self.panCheck.isChecked() is False: return featBobo = feature.geometry().boundingBox() # if scaling and bobo has width and height (i.e. not a point) if self.scaleCheck.isChecked( ) and featBobo.width() != 0 and featBobo.height() != 0: featBobo.scale(self.settings.value("scale")) ul = self.renderer.layerToMapCoordinates( self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMaximum())) ur = self.renderer.layerToMapCoordinates( self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMaximum())) ll = self.renderer.layerToMapCoordinates( self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMinimum())) lr = self.renderer.layerToMapCoordinates( self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMinimum())) x = (ul.x(), ur.x(), ll.x(), lr.x()) y = (ul.y(), ur.y(), ll.y(), lr.y()) x0 = min(x) y0 = min(y) x1 = max(x) y1 = max(y) else: panTo = self.renderer.layerToMapCoordinates( self.layer, featBobo.center()) mapBobo = self.iface.mapCanvas().extent() xshift = panTo.x() - mapBobo.center().x() yshift = panTo.y() - mapBobo.center().y() x0 = mapBobo.xMinimum() + xshift y0 = mapBobo.yMinimum() + yshift x1 = mapBobo.xMaximum() + xshift y1 = mapBobo.yMaximum() + yshift self.iface.mapCanvas().setExtent(QgsRectangle(x0, y0, x1, y1)) self.iface.mapCanvas().refresh() def getCurrentItem(self): i = self.listCombo.currentIndex() if i == -1: return None f = QgsFeature() if self.layer.getFeatures(QgsFeatureRequest().setFilterFid( self.subset[i])).nextFeature(f): return f else: raise NameError("feature not found") def doAction(self, i): f = self.getCurrentItem() self.actionButton.setDefaultAction(self.actionButton.actions()[i]) self.layer.setCustomProperty("teamqgisPreferedAction", self.attrAction[i].name()) self.attrAction.doActionFeature(i, f) def doTranslate(self, trans): # Based on the "doaffine" function in the qgsAffine plugin if (self.layer.geometryType() == 2): start = 1 else: start = 0 if (not self.layer.isEditable()): self.iface.messageBar().pushMessage( "Layer not in edit mode", 'Select a vector layer and choose "Toggle Editing"', level=QgsMessageBar.WARNING) else: feature = self.getCurrentItem() result = feature.geometry() i = start vertex = result.vertexAt(i) fid = feature.id() while (vertex != QgsPoint(0, 0)): newx = vertex.x() + trans[0] * float( self.settings.value("xres")) newy = vertex.y() + trans[1] * float( self.settings.value("yres")) result.moveVertex(newx, newy, i) i += 1 vertex = result.vertexAt(i) self.layer.changeGeometry(fid, result) self.iface.mapCanvas().refresh() self.rubber.reset() self.setRubber(feature) def changeAttribute(self, i, fieldNameComboBox, fieldValueComboBox): fieldValueComboBox.setCurrentIndex(i) feature = self.getCurrentItem() attr_index = self.layer.dataProvider().fieldNameMap()[ fieldNameComboBox.currentText()] self.layer.changeAttributeValue(feature.id(), attr_index, fieldValueComboBox.currentText()) self.iface.mapCanvas().refresh() self.updateValueComboBoxes() def nameComboBox_activated(self, i, nameComboBox): self.layer.setCustomProperty('teamqgis' + nameComboBox.objectName(), nameComboBox.currentText()) @pyqtSlot(name="on_previousButton_clicked") def previousFeature(self): i = self.listCombo.currentIndex() n = max(0, i - 1) self.listCombo.setCurrentIndex(n) self.saveCurrentFeature(n) @pyqtSlot(name="on_nextButton_clicked") def nextFeature(self): self.warned_attr_values = [] # Reset attr warnings i = self.listCombo.currentIndex() c = self.listCombo.count() n = min(i + 1, c - 1) self.listCombo.setCurrentIndex(n) self.saveCurrentFeature(n) @pyqtSlot(int, name="on_listCombo_activated") def saveCurrentFeature(self, i): if self.settings.value("saveSelectionInProject"): self.layer.setCustomProperty("teamqgisCurrentItem", i) @pyqtSlot(int, name="on_fieldOneNameComboBox_activated") def fieldOneNameComboBox_activated(self, i): self.nameComboBox_activated(i, self.fieldOneNameComboBox) @pyqtSlot(int, name="on_fieldTwoNameComboBox_activated") def fieldTwoNameComboBox_activated(self, i): self.nameComboBox_activated(i, self.fieldTwoNameComboBox) @pyqtSlot(int, name="on_fieldThreeNameComboBox_activated") def fieldThreeNameComboBox_activated(self, i): self.nameComboBox_activated(i, self.fieldThreeNameComboBox) @pyqtSlot(int, name="on_listCombo_currentIndexChanged") def on_listCombo_currentIndexChanged(self, i): feature = self.getCurrentItem() if feature is None: return self.rubber.reset() if self.listCombo.count() > 1: self.setRubber(feature) self.updateValueComboBoxes() # scale to feature self.panScaleToItem(feature) # Update browser self.currentPosLabel.setText("%u/%u" % (i + 1, len(self.subset))) # emit signal self.layer.emit(SIGNAL("browserCurrentItem(long)"), feature.id()) @pyqtSlot(int, name="on_panCheck_stateChanged") def on_panCheck_stateChanged(self, i): if self.panCheck.isChecked(): self.scaleCheck.setEnabled(True) feature = self.getCurrentItem() if feature is None: return self.panScaleToItem(feature) else: self.scaleCheck.setEnabled(False) @pyqtSlot(int, name="on_scaleCheck_stateChanged") def on_scaleCheck_stateChanged(self, i): if self.scaleCheck.isChecked(): feature = self.getCurrentItem() if feature is None: return self.panScaleToItem(feature) @pyqtSlot(name="on_editFormButton_clicked") def openFeatureForm(self): if (self.layer.isEditable()): self.layer.commitChanges() else: self.layer.startEditing() @pyqtSlot(name="on_translateRightButton_clicked") def doTranslateRight(self): self.doTranslate((1, 0)) @pyqtSlot(name="on_translateLeftButton_clicked") def doTranslateLeft(self): self.doTranslate((-1, 0)) @pyqtSlot(name="on_translateUpButton_clicked") def doTranslateUp(self): self.doTranslate((0, 1)) @pyqtSlot(name="on_translateDownButton_clicked") def doTranslateDown(self): self.doTranslate((0, -1)) @pyqtSlot(name="on_applyChangesButton_clicked") def applyChanges(self): self.layer.commitChanges() self.layer.startEditing() self.layer.updateExtents() self.iface.mapCanvas().refresh() @pyqtSlot(int, name="on_fieldOneValueComboBox_activated") def on_fieldOneValueComboBox_activated(self, i): self.changeAttribute(i, self.fieldOneNameComboBox, self.fieldOneValueComboBox) @pyqtSlot(int, name="on_fieldTwoValueComboBox_activated") def on_fieldTwoValueComboBox_activated(self, i): self.changeAttribute(i, self.fieldTwoNameComboBox, self.fieldTwoValueComboBox) @pyqtSlot(int, name="on_fieldThreeValueComboBox_activated") def on_fieldThreeValueComboBox_activated(self, i): self.changeAttribute(i, self.fieldThreeNameComboBox, self.fieldThreeValueComboBox)
class quickFinder(QObject): name = u"&Quick Finder" actions = None toolbar = None finders = {} loadingIcon = None def __init__(self, iface): QObject.__init__(self) self.iface = iface self.actions = {} self.finders = {} self.settings = MySettings() self._initFinders() self.iface.projectRead.connect(self._reloadFinders) self.iface.newProjectCreated.connect(self._reloadFinders) # translation environment self.plugin_dir = os.path.dirname(__file__) locale = QSettings().value("locale/userLocale")[0:2] localePath = os.path.join(self.plugin_dir, 'i18n', 'quickfinder_{0}.qm'.format(locale)) if os.path.exists(localePath): self.translator = QTranslator() self.translator.load(localePath) QCoreApplication.installTranslator(self.translator) def initGui(self): self.actions['showSettings'] = QAction( QIcon(":/plugins/quickfinder/icons/settings.svg"), self.tr(u"&Settings"), self.iface.mainWindow()) self.actions['showSettings'].triggered.connect(self.showSettings) self.iface.addPluginToMenu(self.name, self.actions['showSettings']) self.actions['help'] = QAction( QIcon(":/plugins/quickfinder/icons/help.svg"), self.tr("Help"), self.iface.mainWindow()) self.actions['help'].triggered.connect( lambda: QDesktopServices().openUrl( QUrl("http://3nids.github.io/quickfinder"))) self.iface.addPluginToMenu(self.name, self.actions['help']) self._initToolbar() self.rubber = QgsRubberBand(self.iface.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) def unload(self): """ Unload plugin """ for key in self.finders.keys(): self.finders[key].close() for action in self.actions.itervalues(): self.iface.removePluginMenu(self.name, action) if self.toolbar: del self.toolbar if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber def _initToolbar(self): """setup the plugin toolbar.""" self.toolbar = self.iface.addToolBar(self.name) self.toolbar.setObjectName('mQuickFinderToolBar') self.searchAction = QAction(QIcon(":/plugins/quickfinder/icons/magnifier13.svg"), self.tr("Search"), self.toolbar) self.stopAction = QAction( QIcon(":/plugins/quickfinder/icons/wrong2.svg"), self.tr("Cancel"), self.toolbar) self.finderBox = FinderBox(self.finders, self.iface, self.toolbar) self.finderBox.searchStarted.connect(self.searchStarted) self.finderBox.searchFinished.connect(self.searchFinished) self.finderBoxAction = self.toolbar.addWidget(self.finderBox) self.finderBoxAction.setVisible(True) self.searchAction.triggered.connect(self.finderBox.search) self.toolbar.addAction(self.searchAction) self.stopAction.setVisible(False) self.stopAction.triggered.connect(self.finderBox.stop) self.toolbar.addAction(self.stopAction) self.toolbar.setVisible(True) def _initFinders(self): self.finders['geomapfish'] = GeomapfishFinder(self) self.finders['osm'] = OsmFinder(self) self.finders['project'] = ProjectFinder(self) for key in self.finders.keys(): self.finders[key].message.connect(self.displayMessage) self.refreshProject() def _reloadFinders(self): for key in self.finders.keys(): self.finders[key].close() self.finders[key].reload() self.refreshProject() @pyqtSlot(str, QgsMessageBar.MessageLevel) def displayMessage(self, message, level): self.iface.messageBar().pushMessage("QuickFinder", message, level) def showSettings(self): if ConfigurationDialog().exec_(): self._reloadFinders() def searchStarted(self): self.searchAction.setVisible(False) self.stopAction.setVisible(True) def searchFinished(self): self.searchAction.setVisible(True) self.stopAction.setVisible(False) def refreshProject(self): if not self.finders['project'].activated: return if not self.settings.value("refreshAuto"): return nDays = self.settings.value("refreshDelay") # do not ask more ofen than 3 days askLimit = min(3, nDays) recentlyAsked = self.settings.value("refreshLastAsked") >= nDaysAgoIsoDate(askLimit) if recentlyAsked: return threshDate = nDaysAgoIsoDate(nDays) uptodate = True for search in self.finders['project'].searches.values(): if search.dateEvaluated <= threshDate: uptodate = False break if uptodate: return self.settings.setValue("refreshLastAsked", nDaysAgoIsoDate(0)) ret = QMessageBox(QMessageBox.Warning, "Quick Finder", QCoreApplication.translate("Auto Refresh", "Some searches are outdated. Do you want to refresh them ?"), QMessageBox.Cancel | QMessageBox.Yes).exec_() if ret == QMessageBox.Yes: RefreshDialog(self.finders['project']).exec_()
class RectangleMapTool(QgsMapToolEmitPoint): boxCreated = pyqtSignal(QgsRectangle) def __init__(self, canvas): self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) #self.rubberBand.setBorderColor(Qt.red) self.rubberBand.setBrushStyle(Qt.Dense6Pattern) self.rubberBand.setColor(Qt.red) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() if r is not None: self.boxCreated.emit(r) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(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): 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)
class QuickFinder(QObject): name = u"&Quick Finder" actions = None toolbar = None finders = {} loadingIcon = None def __init__(self, iface): QObject.__init__(self) self.iface = iface self.actions = {} self.finders = {} self.settings = MySettings() self.rubber = None self._init_finders() self.iface.projectRead.connect(self._reload_finders) self.iface.newProjectCreated.connect(self._reload_finders) # translation environment self.plugin_dir = os.path.dirname(__file__) locale = QSettings().value("locale/userLocale")[0:2] localePath = os.path.join(self.plugin_dir, 'i18n', 'quickfinder_{0}.qm'.format(locale)) if os.path.exists(localePath): self.translator = QTranslator() self.translator.load(localePath) QCoreApplication.installTranslator(self.translator) def initGui(self): self.actions['showSettings'] = QAction( QIcon(":/plugins/quickfinder/icons/settings.svg"), self.tr(u"&Settings"), self.iface.mainWindow()) self.actions['showSettings'].triggered.connect(self.show_settings) self.iface.addPluginToMenu(self.name, self.actions['showSettings']) self.actions['help'] = QAction( QIcon(":/plugins/quickfinder/icons/help.svg"), self.tr("Help"), self.iface.mainWindow()) self.actions['help'].triggered.connect( lambda: QDesktopServices().openUrl( QUrl("http://3nids.github.io/quickfinder"))) self.iface.addPluginToMenu(self.name, self.actions['help']) self._init_toolbar() self.rubber = QgsRubberBand(self.iface.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) def unload(self): """ Unload plugin """ for key in self.finders.keys(): self.finders[key].close() for action in self.actions.itervalues(): self.iface.removePluginMenu(self.name, action) if self.toolbar: del self.toolbar if self.rubber: self.iface.mapCanvas().scene().removeItem(self.rubber) del self.rubber def _init_toolbar(self): """setup the plugin toolbar.""" self.toolbar = self.iface.addToolBar(self.name) self.toolbar.setObjectName('mQuickFinderToolBar') self.search_action = QAction(QIcon(":/plugins/quickfinder/icons/magnifier13.svg"), self.tr("Search"), self.toolbar) self.stop_action = QAction(QIcon(":/plugins/quickfinder/icons/wrong2.svg"), self.tr("Cancel"), self.toolbar) self.finder_box = FinderBox(self.finders, self.iface, self.toolbar) self.finder_box.search_started.connect(self.search_started) self.finder_box.search_finished.connect(self.search_finished) self.finder_box_action = self.toolbar.addWidget(self.finder_box) self.finder_box_action.setVisible(True) self.search_action.triggered.connect(self.finder_box.search) self.toolbar.addAction(self.search_action) self.stop_action.setVisible(False) self.stop_action.triggered.connect(self.finder_box.stop) self.toolbar.addAction(self.stop_action) self.toolbar.setVisible(True) def _init_finders(self): self.finders['geomapfish'] = GeomapfishFinder(self) self.finders['osm'] = OsmFinder(self) self.finders['project'] = ProjectFinder(self) for key in self.finders.keys(): self.finders[key].message.connect(self.display_message) self.refresh_project() def _reload_finders(self): for key in self.finders.keys(): self.finders[key].close() self.finders[key].reload() self.refresh_project() @pyqtSlot(str, QgsMessageBar.MessageLevel) def display_message(self, message, level): self.iface.messageBar().pushMessage("QuickFinder", message, level) def show_settings(self): if ConfigurationDialog().exec_(): self._reload_finders() def search_started(self): self.search_action.setVisible(False) self.stop_action.setVisible(True) def search_finished(self): self.search_action.setVisible(True) self.stop_action.setVisible(False) def refresh_project(self): if not self.finders['project'].activated: return if not self.settings.value("refreshAuto"): return n_days = self.settings.value("refreshDelay") # do not ask more ofen than 3 days ask_limit = min(3, n_days) recently_asked = self.settings.value("refreshLastAsked") >= n_days_ago_iso_date(ask_limit) if recently_asked: return thresh_date = n_days_ago_iso_date(n_days) uptodate = True for search in self.finders['project'].searches.values(): if search.dateEvaluated <= thresh_date: uptodate = False break if uptodate: return self.settings.setValue("refreshLastAsked", n_days_ago_iso_date(0)) ret = QMessageBox(QMessageBox.Warning, "Quick Finder", QCoreApplication.translate("Auto Refresh", "Some searches are outdated. Do you want to refresh them ?"), QMessageBox.Cancel | QMessageBox.Yes).exec_() if ret == QMessageBox.Yes: RefreshDialog(self.finders['project']).exec_()
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 AddPipeTool(QgsMapTool): def __init__(self, data_dock, params): QgsMapTool.__init__(self, data_dock.iface.mapCanvas()) self.iface = data_dock.iface """:type : QgisInterface""" self.data_dock = data_dock """:type : DataDock""" self.params = params self.mouse_pt = None self.mouse_clicked = False self.first_click = False self.rubber_band = QgsRubberBand(self.canvas(), False) self.rubber_band.setColor(QColor(255, 128, 128)) self.rubber_band.setWidth(1) self.rubber_band.setBrushStyle(Qt.Dense4Pattern) self.rubber_band.reset() self.snapper = None self.snapped_feat_id = None self.snapped_vertex = None self.snapped_vertex_nr = None self.vertex_marker = QgsVertexMarker(self.canvas()) self.elev = None self.diameter_dialog = None def canvasPressEvent(self, event): if event.button() == Qt.RightButton: self.mouse_clicked = False if event.button() == Qt.LeftButton: self.mouse_clicked = True def canvasMoveEvent(self, event): self.mouse_pt = self.toMapCoordinates(event.pos()) last_ix = self.rubber_band.numberOfVertices() self.rubber_band.movePoint(last_ix - 1, (self.snapped_vertex if self.snapped_vertex is not None else self.mouse_pt)) elev = raster_utils.read_layer_val_from_coord(self.params.dem_rlay, self.mouse_pt, 1) self.elev = elev if elev is not None: self.data_dock.lbl_elev_val.setText("{0:.2f}".format(self.elev)) else: self.data_dock.lbl_elev_val.setText('-') # Mouse not clicked: snapping to closest vertex match = self.snapper.snapToMap(self.mouse_pt) if match.isValid(): # Pipe starts from an existing vertex self.snapped_feat_id = match.featureId() self.snapped_vertex = match.point() self.snapped_vertex_nr = match.vertexIndex() self.vertex_marker.setCenter(self.snapped_vertex) self.vertex_marker.setColor(QColor(255, 0, 0)) self.vertex_marker.setIconSize(10) self.vertex_marker.setIconType(QgsVertexMarker.ICON_CIRCLE) self.vertex_marker.setPenWidth(3) self.vertex_marker.show() # else: # It's a new, isolated pipe self.snapped_vertex = None self.snapped_feat_id = None self.vertex_marker.hide() def canvasReleaseEvent(self, event): # if not self.mouse_clicked: # return if event.button() == Qt.LeftButton: # Update rubber bands self.rubber_band.addPoint( (self.snapped_vertex if self.snapped_vertex is not None else self.mouse_pt), True) if self.first_click: self.rubber_band.addPoint( (self.snapped_vertex if self.snapped_vertex is not None else self.mouse_pt), True) self.first_click = not self.first_click elif event.button() == Qt.RightButton: # try: pipe_band_geom = self.rubber_band.asGeometry() # No rubber band geometry and feature snapped: pop the context menu if self.rubber_band.size( ) == 0 and self.snapped_feat_id is not None: menu = QMenu() section_action = menu.addAction('Section...') # TODO: softcode diameter_action = menu.addAction( 'Change diameter...') # TODO: softcode action = menu.exec_(self.iface.mapCanvas().mapToGlobal( QPoint(event.pos().x(), event.pos().y()))) pipe_ft = vector_utils.get_feats_by_id(self.params.pipes_vlay, self.snapped_feat_id)[0] if action == section_action: if self.params.dem_rlay is None: self.iface.messageBar().pushMessage( Parameters.plug_in_name, 'No DEM selected. Cannot edit section.', Qgis.Warning, 5) # TODO: softcode else: # Check whether the pipe is all inside the DEM pipe_pts = pipe_ft.geometry().asPolyline() for pt in pipe_pts: if not self.params.dem_rlay.extent().contains(pt): self.iface.messageBar().pushMessage( Parameters.plug_in_name, 'Some pipe vertices fall outside of the DEM. Cannot edit section.', Qgis.Warning, 5) # TODO: softcode return # Check whether the start/end nodes have an elevation value (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, pipe_ft.geometry()) start_node_elev = start_node_ft.attribute( Junction.field_name_elev) end_node_elev = end_node_ft.attribute( Junction.field_name_elev) if start_node_elev == NULL or end_node_elev == NULL: self.iface.messageBar().pushMessage( Parameters.plug_in_name, 'Missing elevation value in start or end node attributes. Cannot edit section.', Qgis.Warning, 5) # TODO: softcode return pipe_dialog = PipeSectionDialog( self.iface.mainWindow(), self.iface, self.params, pipe_ft) pipe_dialog.exec_() elif action == diameter_action: old_diam = pipe_ft.attribute(Pipe.field_name_diameter) self.diameter_dialog = DiameterDialog( self.iface.mainWindow(), self.params, old_diam) self.diameter_dialog.exec_() # Exec creates modal dialog new_diameter = self.diameter_dialog.get_diameter() if new_diameter is None: return # Update pipe diameter vector_utils.update_attribute(self.params.pipes_vlay, pipe_ft, Pipe.field_name_diameter, new_diameter) # Check if a valve is present adj_valves = NetworkUtils.find_links_adjacent_to_link( self.params, self.params.pipes_vlay, pipe_ft, True, True, False) if adj_valves['valves']: self.iface.messageBar().pushMessage( Parameters.plug_in_name, 'Valves detected on the pipe: need to update their diameters too.', Qgis.Warning, 5) # TODO: softcode return # There's a rubber band: create new pipe if pipe_band_geom is not None: #XPSS ADDED - transform rubber band CRS canvas_crs = self.canvas().mapSettings().destinationCrs() #pipes_vlay = Parameters().pipes_vlay() #print(pipes_vlay) qepanet_crs = self.params.pipes_vlay.sourceCrs() crs_transform = QgsCoordinateTransform(canvas_crs, qepanet_crs, QgsProject.instance()) pipe_band_geom.transform(crs_transform) # Finalize line rubberband_pts = pipe_band_geom.asPolyline()[:-1] if len(rubberband_pts) == 0: self.iface.mapCanvas().refresh() return rubberband_pts = self.remove_duplicated_point(rubberband_pts) if len(rubberband_pts) < 2: self.rubber_band.reset() return # Check whether the pipe points are located on existing nodes junct_nrs = [0] for p in range(1, len(rubberband_pts) - 1): overlapping_nodes = NetworkUtils.find_overlapping_nodes( self.params, rubberband_pts[p]) if overlapping_nodes['junctions'] or overlapping_nodes[ 'reservoirs'] or overlapping_nodes['tanks']: junct_nrs.append(p) junct_nrs.append(len(rubberband_pts) - 1) new_pipes_nr = len(junct_nrs) - 1 new_pipes_fts = [] new_pipes_eids = [] for np in range(new_pipes_nr): pipe_eid = NetworkUtils.find_next_id( self.params.pipes_vlay, Pipe.prefix) # TODO: softcode #demand = float(self.data_dock.txt_pipe_demand.text()) length_units = 'm' #TODO soft code diameter = float(self.data_dock.cbo_pipe_dia.\ currentText()) diameter_units = self.data_dock.cbo_pipe_dia_units.\ currentText() #loss = float(self.data_dock.txt_pipe_loss.text()) #status = self.data_dock.cbo_pipe_status.currentText() material = self.data_dock.cbo_pipe_mtl.currentText() roughness = float(self.data_dock.txt_roughness.text()) #pipe_desc = self.data_dock.txt_pipe_desc.text() #pipe_tag = self.data_dock.cbo_pipe_tag.currentText() num_edu = 1 zone_id = 0 velocity = 0 velocity_units = 'm/s' frictionloss = 0 frictionloss_units = 'm' pipe_ft = LinkHandler.create_new_pipe( self.params, pipe_eid, length_units, diameter, diameter_units, 0, roughness, " ", material, rubberband_pts[junct_nrs[np]:junct_nrs[np + 1] + 1], True, " ", " ", num_edu, zone_id, velocity, velocity_units, frictionloss, frictionloss_units) self.rubber_band.reset() new_pipes_fts.append(pipe_ft) new_pipes_eids.append(pipe_eid) # emitter_coeff_s = self.data_dock.txt_junction_emit_coeff.text() # # if emitter_coeff_s is None or emitter_coeff_s == '': # emitter_coeff = float(0) # else: # emitter_coeff = float(self.data_dock.txt_junction_emit_coeff.text()) # # Description # junction_desc = self.data_dock.txt_junction_desc.text() # # # Tag # junction_tag = self.data_dock.cbo_junction_tag.currentText() zone_end = 0 pressure = 0 pressure_units = self.data_dock.cbo_rpt_units_pressure.currentText( ) # Create start and end node, if they don't exist (start_junction, end_junction) = NetworkUtils.find_start_end_nodes( self.params, new_pipes_fts[0].geometry()) new_start_junction = None if not start_junction: new_start_junction = rubberband_pts[0] junction_eid = NetworkUtils.find_next_id( self.params.junctions_vlay, Junction.prefix) elev = raster_utils.read_layer_val_from_coord( self.params.dem_rlay, new_start_junction, 1) if elev is None: elev = 0 # If elev is none, and the DEM is selected, it's better to inform the user if self.params.dem_rlay is not None: self.iface.messageBar().pushMessage( Parameters.plug_in_name, 'Elevation value not available: element elevation set to 0.', Qgis.Warning, 5) # TODO: softcode deltaz = float(0) #j_demand = float(self.data_dock.txt_junction_demand.text()) # pattern = self.data_dock.cbo_junction_pattern.itemData( # self.data_dock.cbo_junction_pattern.currentIndex()) # if pattern is not None: # pattern_id = pattern.id # else: # pattern_id = None NodeHandler.create_new_junction(self.params, new_start_junction, junction_eid, elev, 0, deltaz, None, 0, " ", " ", zone_end, pressure, pressure_units) (start_junction, end_junction) = NetworkUtils.find_start_end_nodes( self.params, new_pipes_fts[len(new_pipes_fts) - 1].geometry()) new_end_junction = None if not end_junction: new_end_junction = rubberband_pts[len(rubberband_pts) - 1] junction_eid = NetworkUtils.find_next_id( self.params.junctions_vlay, Junction.prefix) elev = raster_utils.read_layer_val_from_coord( self.params.dem_rlay, new_end_junction, 1) if elev is None: elev = 0 # If elev is none, and the DEM is selected, it's better to inform the user if self.params.dem_rlay is not None: self.iface.messageBar().pushMessage( Parameters.plug_in_name, 'Elevation value not available: element elevation set to 0.', Qgis.Warning, 5) # TODO: softcode deltaz = float(0) # pattern = self.data_dock.cbo_junction_pattern.itemData( # self.data_dock.cbo_junction_pattern.currentIndex()) # if pattern is not None: # pattern_id = pattern.id # else: # pattern_id = None NodeHandler.create_new_junction(self.params, new_end_junction, junction_eid, elev, 0, deltaz, None, 0, " ", " ", zone_end, pressure, pressure_units) # If end or start node intersects a pipe, split it if new_start_junction: for pipe_ft in self.params.pipes_vlay.getFeatures(): if pipe_ft.attribute(Pipe.field_name_eid) != new_pipes_eids[0] and\ pipe_ft.geometry().distance(QgsGeometry.fromPointXY(new_start_junction)) < self.params.tolerance: LinkHandler.split_pipe(self.params, pipe_ft, new_start_junction) if new_end_junction: for pipe_ft in self.params.pipes_vlay.getFeatures(): if pipe_ft.attribute(Pipe.field_name_eid) != new_pipes_eids[-1] and\ pipe_ft.geometry().distance(QgsGeometry.fromPointXY(new_end_junction)) < self.params.tolerance: LinkHandler.split_pipe(self.params, pipe_ft, new_end_junction) self.iface.mapCanvas().refresh() # except Exception as e: # self.rubber_band.reset() # self.iface.messageBar().pushWarning('Cannot add new pipe to ' + self.params.pipes_vlay.name() + ' layer', repr(e)) # traceback.print_exc(file=sys.stdout) def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.rubber_band.reset() def activate(self): self.update_snapper() # Editing if not self.params.junctions_vlay.isEditable(): self.params.junctions_vlay.startEditing() if not self.params.pipes_vlay.isEditable(): self.params.pipes_vlay.startEditing() def deactivate(self): # QgsProject.instance().setSnapSettingsForLayer(self.params.junctions_vlay.id(), # True, # QgsSnapper.SnapToVertex, # QgsTolerance.MapUnits, # 0, # True) # # QgsProject.instance().setSnapSettingsForLayer(self.params.reservoirs_vlay.id(), # True, # QgsSnapper.SnapToVertex, # QgsTolerance.MapUnits, # 0, # True) # QgsProject.instance().setSnapSettingsForLayer(self.params.tanks_vlay.id(), # True, # QgsSnapper.SnapToVertex, # QgsTolerance.MapUnits, # 0, # True) # QgsProject.instance().setSnapSettingsForLayer(self.params.pipes_vlay.id(), # True, # QgsSnapper.SnapToSegment, # QgsTolerance.MapUnits, # 0, # True) # self.rubber_band.reset() self.data_dock.btn_add_pipe.setChecked(False) self.canvas().scene().removeItem(self.vertex_marker) def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True def reset_marker(self): self.outlet_marker.hide() self.canvas().scene().removeItem(self.outlet_marker) def remove_duplicated_point(self, pts): # This is needed because the rubber band sometimes returns duplicated points purged_pts = [pts[0]] for p in enumerate(list(range(len(pts) - 1)), 1): if pts[p[0]] == pts[p[0] - 1]: continue else: purged_pts.append(pts[p[0]]) return purged_pts def update_snapper(self): layers = { self.params.junctions_vlay: QgsSnappingConfig.Vertex, self.params.reservoirs_vlay: QgsSnappingConfig.Vertex, self.params.tanks_vlay: QgsSnappingConfig.Vertex, self.params.pipes_vlay: QgsSnappingConfig.VertexAndSegment } self.snapper = NetworkUtils.set_up_snapper(layers, self.iface.mapCanvas(), self.params.snap_tolerance) self.snapper.toggleEnabled() # Needed by Observable def update(self, observable): self.update_snapper()
class BanLocatorFilter(QgsLocatorFilter): HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS BAN Locator Filter'} message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget) def __init__(self, iface: QgisInterface = 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.rubber_band = None self.feature_rubber_band = None self.iface = iface self.map_canvas = None self.settings = Settings() self.transform_4326 = None self.current_timer = None self.crs = None self.event_loop = None self.result_found = False self.search_delay = 0.5 self.dbg_info("initialisation") 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 'Recherche Adresse BAN' def clone(self): return BanLocatorFilter(self.iface) def priority(self): return self.settings.value('locations_priority') def displayName(self): return self.tr('Ban Adress Location') def prefix(self): return 'ban' def clearPreviousResults(self): self.rubber_band.reset(QgsWkbTypes.PointGeometry) self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry) 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): ConfigDialog(parent).exec_() def create_transforms(self): # this should happen in the main thread dst_crs = self.map_canvas.mapSettings().destinationCrs() src_crs_4326 = QgsCoordinateReferenceSystem('EPSG:4326') self.transform_4326 = QgsCoordinateTransform(src_crs_4326, dst_crs, QgsProject.instance()) def group_info(self, group: str) -> str: return TYPE_RESULT[group] @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 Ban locator search...") if len(search) < 5: return self.result_found = False url = 'https://api-adresse.data.gouv.fr/search/' params = { 'q': str(search), 'limit': str(self.settings.value('locations_limit')) } # Locations, WMS layers nam = NetworkAccessManager() feedback.canceled.connect(nam.abort) url = self.url_with_param(url, params) self.dbg_info(url) try: (response, content) = nam.request(url, headers=self.HEADERS, blocking=True) self.handle_response(response) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(err) 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 handle_response(self, response): 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) for loc in data['features']: importance = loc['properties']['importance'] citycode = loc['properties']['citycode'] score = loc['properties']['score'] type_result = loc['properties']['type'] label = loc['properties']['label'] x = float(loc['geometry']['coordinates'][0]) y = float(loc['geometry']['coordinates'][1]) result = QgsLocatorResult() result.filter = self result.displayString = label result.group = self.group_info(type_result) result.icon = QIcon( ":/plugins/swiss_locator/icons/ban_locator.png") result.userData = LocationResult(importance, citycode, score, type_result, label, x, y) 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() if type(result.userData) == NoResult: pass else: point = QgsGeometry.fromPointXY(result.userData.point) if not point: return point.transform(self.transform_4326) self.highlight(point, result.userData.type_result) if not self.settings.value('keep_marker_visible'): self.current_timer = QTimer() self.current_timer.timeout.connect(self.clearPreviousResults) self.current_timer.setSingleShot(True) self.current_timer.start(8000) def highlight(self, point, type_result=None): self.rubber_band.reset(QgsWkbTypes.PointGeometry) self.rubber_band.addGeometry(point, None) if type_result in ('housenumber', 'street'): zoom = float(self.settings.value('location_housenumber_zoom')) else: zoom = float(self.settings.value('location_default_zoom')) self.map_canvas.setCenter(point.asPoint()) self.map_canvas.zoomScale(zoom) def info(self, msg="", level=Qgis.Info, emit_message: bool = False): self.logMessage(str(msg), level) if emit_message: self.message_emitted.emit(msg, level) def dbg_info(self, msg=""): if DEBUG: self.info(msg)