def onResultTableSelChanged(self): cur_row_index = self.dockWidget.tableResult.currentRow() if cur_row_index > -1: self.clearHighlight(self.intersected_h_list) self.clearHighlight(self.intersection_h_list) f_geometry = QgsGeometry() f_geometry = QgsGeometry.fromWkt( self.dockWidget.tableResult.item(cur_row_index, 1).text()) h = QgsHighlight(self.iface.mapCanvas(), f_geometry, self.inters_layer) h.setColor(QColor(26, 200, 0, 220)) h.setWidth(6) h.setFillColor(QColor(26, 200, 0, 150)) self.intersected_h_list.append(h) if_geometry = QgsGeometry() if_geometry = QgsGeometry.fromWkt( self.dockWidget.tableResult.item(cur_row_index, 2).text()) ih = QgsHighlight(self.iface.mapCanvas(), if_geometry, self.inters_layer) ih.setColor(QColor(230, 0, 0, 220)) ih.setWidth(6) ih.setFillColor(QColor(230, 0, 0, 150)) self.intersection_h_list.append(ih)
class DigitizingToolsChooseRemaining(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface, editLayer, pkValues, featDict, title): QtWidgets.QDialog.__init__(self) self.setupUi(self) self.iface = iface self.editLayer = editLayer self.pkValues = pkValues self.featDict = featDict self.chooseId.addItems(list(self.pkValues.keys())) self.setWindowTitle(title) self.label.setText(QtWidgets.QApplication.translate( "digitizingtools", "Choose which already existing feature should remain")) self.buttonBox.rejected.connect(self.reject) self.buttonBox.accepted.connect(self.accept) @QtCore.pyqtSlot(int) def on_chooseId_currentIndexChanged(self, thisIndex): aPkValue = self.chooseId.currentText() aGeom = self.featDict[self.pkValues[aPkValue]].geometry() hlColor, hlFillColor, hlBuffer, hlMinWidth = dtGetHighlightSettings() self.hl = QgsHighlight(self.iface.mapCanvas(), aGeom, self.editLayer) self.hl.setColor(hlColor) self.hl.setFillColor(hlFillColor) self.hl.setBuffer(hlBuffer) self.hl.setWidth(hlMinWidth) @QtCore.pyqtSlot() def reject(self): self.done(0) @QtCore.pyqtSlot() def accept(self): self.pkValueToKeep = self.chooseId.currentText() self.done(1)
def runTestForLayer(self, layer, testname): tempdir = tempfile.mkdtemp() layer = QgsVectorLayer(layer, 'Layer', 'ogr') QgsProject.instance().addMapLayer(layer) self.iface.mapCanvas().setExtent(layer.extent()) geom = next(layer.getFeatures()).geometry() highlight = QgsHighlight(self.iface.mapCanvas(), geom, layer) color = QColor(Qt.red) highlight.setColor(color) highlight.setWidth(2) color.setAlpha(50) highlight.setFillColor(color) highlight.show() image = QImage(QSize(400, 400), QImage.Format_ARGB32) image.fill(Qt.white) painter = QPainter() painter.begin(image) self.iface.mapCanvas().render(painter) painter.end() control_image = os.path.join(tempdir, 'highlight_{}.png'.format(testname)) image.save(control_image) checker = QgsRenderChecker() checker.setControlPathPrefix("highlight") checker.setControlName("expected_highlight_{}".format(testname)) checker.setRenderedImage(control_image) self.assertTrue(checker.compareImages("highlight_{}".format(testname))) shutil.rmtree(tempdir)
def highlightFeature(self, theFeature): highlight = QgsHighlight(self.mTheCanvas, theFeature.geometry(), self.mTheLayer) highlight.setColor(QColor(255,0,0,128)) highlight.setFillColor(QColor(255,0,0,128)) highlight.setBuffer(0.5) highlight.setMinWidth(6) highlight.setWidth(6) highlight.show() self.highlightList.append(highlight) return
def highlightByGeometry(self, geometry, color=QColor(255, 0, 0, 128)): highlight = QgsHighlight(self.mTheCanvas, geometry, self.mTheLayer) highlight.setColor(color) highlight.setFillColor(color) highlight.setBuffer(0.5) highlight.setMinWidth(6) highlight.setWidth(6) highlight.show() self.highlightList.append(highlight) return
def markFeature(self, lay, feat): try: color = QColor(Qt.red) highlight = QgsHighlight(self.iface.mapCanvas(), feat, lay) highlight.setColor(color) color.setAlpha(50) highlight.setFillColor(color) highlight.show() return highlight except Exception as e: self.info.err(e)
def _addHighlight(self, canvas, geometry, layer): hl = QgsHighlight(canvas, geometry, layer) color = QColor(QSettings().value('/Map/highlight/color', QGis.DEFAULT_HIGHLIGHT_COLOR.name(), str)) alpha = QSettings().value('/Map/highlight/colorAlpha', QGis.DEFAULT_HIGHLIGHT_COLOR.alpha(), int) buff = QSettings().value('/Map/highlight/buffer', QGis.DEFAULT_HIGHLIGHT_BUFFER_MM, float) minWidth = QSettings().value('/Map/highlight/minWidth', QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM, float) hl.setColor(color) color.setAlpha(alpha) hl.setFillColor(color) hl.setBuffer(buff) hl.setMinWidth(minWidth) self._highlights.append(hl)
def test_feature_transformation(self): poly_shp = os.path.join(TEST_DATA_DIR, 'polys.shp') layer = QgsVectorLayer(poly_shp, 'Layer', 'ogr') sub_symbol = QgsFillSymbol.createSimple({ 'color': '#8888ff', 'outline_style': 'no' }) sym = QgsFillSymbol() buffer_layer = QgsGeometryGeneratorSymbolLayer.create( {'geometryModifier': 'buffer($geometry, -0.4)'}) buffer_layer.setSymbolType(QgsSymbol.Fill) buffer_layer.setSubSymbol(sub_symbol) sym.changeSymbolLayer(0, buffer_layer) layer.setRenderer(QgsSingleSymbolRenderer(sym)) canvas = QgsMapCanvas() canvas.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857')) canvas.setFrameStyle(0) canvas.resize(600, 400) self.assertEqual(canvas.width(), 600) self.assertEqual(canvas.height(), 400) canvas.setLayers([layer]) canvas.setExtent(QgsRectangle(-11960254, 4247568, -11072454, 4983088)) canvas.show() # need to wait until first redraw can occur (note that we first need to wait till drawing starts!) while not canvas.isDrawing(): app.processEvents() canvas.waitWhileRendering() feature = layer.getFeature(1) self.assertTrue(feature.isValid()) highlight = QgsHighlight(canvas, feature, layer) color = QColor(Qt.red) highlight.setColor(color) color.setAlpha(50) highlight.setFillColor(color) highlight.show() highlight.show() self.assertTrue( self.canvasImageCheck('highlight_transform', 'highlight_transform', canvas))
class DigitizingToolsChooseRemaining(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface, editLayer, pkValues, featDict, title): QtWidgets.QDialog.__init__(self) self.setupUi(self) self.iface = iface self.editLayer = editLayer self.pkValues = pkValues self.featDict = featDict self.chooseId.addItems(list(self.pkValues.keys())) self.setWindowTitle(title) self.label.setText( QtWidgets.QApplication.translate( "digitizingtools", "Choose which already existing feature should remain")) self.buttonBox.rejected.connect(self.reject) self.buttonBox.accepted.connect(self.accept) def clearHighlight(self): try: self.hl.hide() except: pass @QtCore.pyqtSlot(int) def on_chooseId_currentIndexChanged(self, thisIndex): self.clearHighlight() aPkValue = self.chooseId.currentText() aGeom = self.featDict[self.pkValues[aPkValue]].geometry() hlColor, hlFillColor, hlBuffer, hlMinWidth = dtGetHighlightSettings() self.hl = QgsHighlight(self.iface.mapCanvas(), aGeom, self.editLayer) self.hl.setColor(hlColor) self.hl.setFillColor(hlFillColor) self.hl.setBuffer(hlBuffer) self.hl.setWidth(hlMinWidth) self.hl.show() @QtCore.pyqtSlot() def reject(self): self.clearHighlight() self.done(0) @QtCore.pyqtSlot() def accept(self): self.clearHighlight() self.pkValueToKeep = self.chooseId.currentText() self.done(1)
def highlight_courant(self): """highlight animated flowline layer where Courant number is higher than a given value (use velocity variable as flowline-results)""" if len(QgsProject.instance().mapLayersByName("line_results")) == 0: self.iface.messageBar().pushMessage( "Warning", 'Couldn\'t find line_results layer, click "Animation on" button', level=Qgis.Warning, ) return # layer found canvas = self.iface.mapCanvas() line_results = QgsProject.instance().mapLayersByName("line_results")[0] global_settings_layer = QgsProject.instance().mapLayersByName( "v2_global_settings" )[0] timestep = list(global_settings_layer.getFeatures())[0][ "sim_time_step" ] # [0] -> [self.selected_scenario_index] d = QgsDistanceArea() d.setEllipsoid("WGS84") features = line_results.getFeatures() for feature in features: kcu = feature["kcu"] if kcu in [0, 1, 2, 3, 5, 100, 101]: geometry = feature.geometry() length = d.measureLength(geometry) velocity = abs(feature["result"]) courant = velocity * timestep / length if courant > self.courantThreshold.value(): color = QtGui.QColor(Qt.red) highlight = QgsHighlight(canvas, feature, line_results) highlight.setColor(color) highlight.setMinWidth(courant / 2) # highlight.setBuffer() color.setAlpha(50) highlight.setFillColor(color) highlight.show() self.highlights.append(highlight)
def onMapClickedTableSelChanged(self): cur_row_index = self.map_clicked_dlg.tableClickedWays.currentRow() if cur_row_index > -1: self.clearAllHighlights() f_geometry = QgsGeometry() f_geometry = QgsGeometry.fromWkt( self.map_clicked_dlg.tableClickedWays.item(cur_row_index, 1).text()) h = QgsHighlight(self.iface.mapCanvas(), f_geometry, self.current_layer) h.setColor(QColor(0, 15, 183, 220)) h.setWidth(6) h.setFillColor(QColor(0, 15, 183, 150)) self.mapclicked_h_list.append(h) self.setButtonOkStatus()
class FeatureSelectorWidget(QWidget): feature_identified = pyqtSignal(QgsFeature) def __init__(self, parent): QWidget.__init__(self, parent) edit_layout = QHBoxLayout() edit_layout.setContentsMargins(0, 0, 0, 0) edit_layout.setSpacing(2) self.setLayout(edit_layout) self.line_edit = QLineEdit(self) self.line_edit.setReadOnly(True) edit_layout.addWidget(self.line_edit) self.highlight_feature_button = QToolButton(self) self.highlight_feature_button.setPopupMode(QToolButton.MenuButtonPopup) self.highlight_feature_action = QAction( QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"), "Highlight feature", self) self.scale_highlight_feature_action = QAction( QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"), "Scale and highlight feature", self) self.pan_highlight_feature_action = QAction( QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"), "Pan and highlight feature", self) self.highlight_feature_button.addAction(self.highlight_feature_action) self.highlight_feature_button.addAction( self.scale_highlight_feature_action) self.highlight_feature_button.addAction( self.pan_highlight_feature_action) self.highlight_feature_button.setDefaultAction( self.highlight_feature_action) edit_layout.addWidget(self.highlight_feature_button) self.map_identification_button = QToolButton(self) self.map_identification_button.setIcon( QgsApplication.getThemeIcon("/mActionMapIdentification.svg")) self.map_identification_button.setText("Select on map") self.map_identification_button.setCheckable(True) edit_layout.addWidget(self.map_identification_button) self.map_identification_button.clicked.connect(self.map_identification) self.highlight_feature_button.triggered.connect( self.highlight_action_triggered) self.layer = None self.map_tool = None self.canvas = None self.window_widget = None self.highlight = None self.feature = QgsFeature() def set_canvas(self, map_canvas): self.map_tool = QgsMapToolIdentifyFeature(map_canvas) self.map_tool.setButton(self.map_identification_button) self.canvas = map_canvas def set_layer(self, layer): self.layer = layer def set_feature(self, feature, canvas_extent=CanvasExtent.Fixed): self.line_edit.clear() self.feature = feature if self.feature is None or not self.feature.isValid( ) or self.layer is None: return expression = QgsExpression(self.layer.displayExpression()) context = QgsExpressionContext() scope = QgsExpressionContextScope() context.appendScope(scope) scope.setFeature(feature) feature_title = expression.evaluate(context) if feature_title == "": feature_title = feature.id() self.line_edit.setText(str(feature_title)) self.highlight_feature(canvas_extent) def clear(self): self.feature = QgsFeature() self.line_edit.clear() @pyqtSlot() def map_identification(self): if self.layer is None or self.map_tool is None or self.canvas is None: return self.map_tool.setLayer(self.layer) self.canvas.setMapTool(self.map_tool) self.window_widget = QWidget.window(self) self.canvas.window().raise_() self.canvas.activateWindow() self.canvas.setFocus() self.map_tool.featureIdentified.connect( self.map_tool_feature_identified) self.map_tool.deactivated.connect(self.map_tool_deactivated) def map_tool_feature_identified(self, feature): feature = QgsFeature(feature) self.feature_identified.emit(feature) self.unset_map_tool() self.set_feature(feature) def map_tool_deactivated(self): if self.window_widget is not None: self.window_widget.raise_() self.window_widget.activateWindow() def highlight_feature(self, canvas_extent=CanvasExtent.Fixed): if self.canvas is None or not self.feature.isValid(): return geom = self.feature.geometry() if geom is None: return if canvas_extent == CanvasExtent.Scale: feature_bounding_box = geom.boundingBox() feature_bounding_box = self.canvas.mapSettings( ).layerToMapCoordinates(self.layer, feature_bounding_box) extent = self.canvas.extent() if not extent.contains(feature_bounding_box): extent.combineExtentWith(feature_bounding_box) extent.scale(1.1) self.canvas.setExtent(extent) self.canvas.refresh() elif canvas_extent == CanvasExtent.Pan: centroid = geom.centroid() center = centroid.asPoint() center = self.canvas.mapSettings().layerToMapCoordinates( self.layer, center) self.canvas.zoomByFactor(1.0, center) # refresh is done in this method # highlight self.delete_highlight() self.highlight = QgsHighlight(self.canvas, geom, self.layer) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", Qgis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int( settings.value("/Map/highlight/colorAlpha", Qgis.DEFAULT_HIGHLIGHT_COLOR.alpha())) buffer = 2 * float( settings.value("/Map/highlight/buffer", Qgis.DEFAULT_HIGHLIGHT_BUFFER_MM)) min_width = 2 * float( settings.value("/Map/highlight/min_width", Qgis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.highlight.setColor(color) # sets also fill with default alpha color.setAlpha(alpha) self.highlight.setFillColor(color) # sets fill with alpha self.highlight.setBuffer(buffer) self.highlight.setMinWidth(min_width) self.highlight.setWidth(4.0) self.highlight.show() self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(self.delete_highlight) self.timer.start(3000) def delete_highlight(self): if self.highlight is not None: self.highlight.hide() del self.highlight self.highlight = None def unset_map_tool(self): if self.canvas is not None and self.map_tool is not None: # this will call mapTool.deactivated self.canvas.unsetMapTool(self.map_tool) def highlight_action_triggered(self, action): self.highlight_feature_button.setDefaultAction(action) if action == self.highlight_feature_action: self.highlight_feature() elif action == self.scale_highlight_feature_action: self.highlight_feature(CanvasExtent.Scale) elif action == self.pan_highlight_feature_action: self.highlight_feature(CanvasExtent.Pan)
class TinTools: def __init__(self, iface): self.iface = iface self.toolbar = None self.highlight = None self.msgBar = iface.messageBar() self.plugin_dir = os.path.dirname(__file__) print('plugin init en ' + self.plugin_dir) def initGui(self): self.toolbar = self.iface.addToolBar(u'TIN Tools') self.toolbar.setObjectName(u'tintools') label = QLabel('Capa TIN:') self.toolbar.addWidget(label) self.tin_cb = QgsMapLayerComboBox() self.tin_cb.setMinimumWidth(200) self.tin_cb.setFilters(QgsMapLayerProxyModel.VectorLayer) self.tin_cb.layerChanged.connect(self.tinLayerChanged) self.toolbar.addWidget(self.tin_cb) # icon_path = self.plugin_dir +'/icon.png' # icon = QIcon(icon_path) #'\u0394 \u2B16 \u20E4 \u2350 \u21C8 \u2963 \u2B7F \u2b85 \u23C4 \u25e9' self.calc_plane_action = QAction('\u0394', self.iface.mainWindow()) self.calc_plane_action.triggered.connect(self.calcPlaneEquation) self.toolbar.addAction(self.calc_plane_action) self.calc_z = QAction('\u21C8', self.iface.mainWindow()) self.calc_z.triggered.connect(self.calcZ) self.toolbar.addAction(self.calc_z) self.adjust_to_tin = QAction('\u25e9', self.iface.mainWindow()) self.adjust_to_tin.triggered.connect(self.adjustToTin) self.toolbar.addAction(self.adjust_to_tin) self.tinLayerChanged(self.tin_cb.currentLayer()) def unload(self): self.planeCalc = None self.setHighlight(None, None) self.iface.removeToolBarIcon(self.calc_plane_action) self.iface.removeToolBarIcon(self.calc_z) self.iface.removeToolBarIcon(self.adjust_to_tin) del self.toolbar print('unload toolbar5') def msg(self, text): self.msgBar.pushWarning('TIN tool', text) def info(self, text): self.msgBar.pushInfo('TIN tool', text) def setHighlight(self, ly, vertex): if self.highlight: self.highlight.hide() self.highlight = None if vertex: color = QColor(255, 0, 100, 255) lv = vertex + [vertex[0]] g = QgsGeometry.fromPolyline(lv).convertToType(2) print(g.asWkt(3)) self.highlight = QgsHighlight(self.iface.mapCanvas(), g, ly) self.highlight.setColor(color) self.highlight.setWidth(5) color.setAlpha(50) self.highlight.setFillColor(color) self.highlight.show() def tinLayerChanged(self, layer): self.calc_plane_action.setEnabled(layer is not None) self.adjust_to_tin.setEnabled(layer is not None) self.calc_z.setEnabled(False) self.planeCalc = None self.setHighlight(None, None) def calcPlaneEquation(self): ly = self.tin_cb.currentLayer() self.setHighlight(None, None) if not ly: self.msg('No hay ninguna capa seleccionada') return ver = None feat = ly.selectedFeatures() geometry_type = ly.geometryType() if geometry_type == 0: # point if len(feat) == 3: ver = [f.geometry().vertexAt(0) for f in feat] else: self.msg('Hay que seleccionar 3 puntos') elif geometry_type == 2: #polygon if len(feat) == 1: geom = feat[0].geometry() ver = list(geom.vertices()) if len(ver) == 4: ver = ver[:3] else: self.msg('El polígono tiene que ser un triángulo') else: self.msg('Seleccionar solo un triángulo') else: self.msg('seleccionar tres puntos o un triángulo') if ver: self.planeCalc = PlaneCalc(ver) self.info( 'Selecciona los elementos de una capa para calcular su Z en el plano' ) self.calc_z.setEnabled(True) self.setHighlight(ly, ver) else: self.planeCalc = None self.calc_z.setEnabled(False) def calcZ(self): layer = self.iface.activeLayer() if not layer: QMessageBox.warning(None, 'TIN Tools', 'Selecciona elemenos de una capa') return if not layer.isEditable(): QMessageBox.information(None, 'TIN cal', 'La capa no está en modo edición') return for f in layer.getSelectedFeatures(): geom = f.geometry() n = 0 v = geom.vertexAt(0) while (v != QgsPoint(0, 0)): z = self.planeCalc.cal_z(v.x(), v.y()) v.setZ(z) geom.moveVertex(v, n) n += 1 v = geom.vertexAt(n) layer.changeGeometry(f.id(), geom) def adjustToTin(self): print('calc tin')
class SpatialPreview(QTabWidget, Ui_frmPropertyPreview): """ Widget for previewing spatial unit on either local map or web source. """ def __init__(self, parent=None, iface=None): QTabWidget.__init__(self, parent) self.setupUi(self) self._notif_bar = None self._ol_loaded = False self._overlay_layer = None self.sel_highlight = None self.memory_layer = None self._db_session = STDMDb.instance().session self.set_iface(iface) #Web config self._web_spatial_loader = WebSpatialLoader(self.spatial_web_view, self) #Connect signals self._web_spatial_loader.loadError.connect(self.on_spatial_browser_error) self._web_spatial_loader.loadProgress.connect(self.on_spatial_browser_loading) self._web_spatial_loader.loadFinished.connect(self.on_spatial_browser_finished) self._web_spatial_loader.zoomChanged.connect(self.on_map_zoom_level_changed) self.rbGMaps.toggled.connect(self.on_load_GMaps) self.rbOSM.toggled.connect(self.on_load_OSM) self.zoomSlider.sliderReleased.connect(self.on_zoom_changed) self.btnResetMap.clicked.connect(self.on_reset_web_map) self.btnSync.clicked.connect(self.on_sync_extents) QgsMapLayerRegistry.instance().layersWillBeRemoved.connect(self._on_overlay_to_be_removed) def set_iface(self, iface): self._iface = iface self.local_map.set_iface(iface) def set_notification_bar(self, notif_bar): """ Set notification widget. :param notif_bar: Notification widget. """ self._notif_bar = notif_bar def notification_bar(self): """ :return: Currently configured notification bar. """ return self._notif_bar def _insert_notification(self, msg, level, clear_first = True): if self._notif_bar is None: return if clear_first: self._notif_bar.clear() self._notif_bar.insertNotification(msg, level) def iface(self): return self._iface def _setDefaults(self): """ Set default settings """ self.set_canvas_background_color(self.canvasBgColor) def set_canvas_background_color(self,color): """ Set the background color of the map canvas """ self.localMap.setCanvasColor(color) self.canvasBgColor = color def refresh_canvas_layers(self, process_events=False): """ Reload map layers in the viewer canvas. """ self.local_map.refresh_layers() def load_web_map(self): """ Loads the web map into the view using canvas extents if there are existing layers in the map canvas. """ if not self._ol_loaded: self._web_spatial_loader.load() def _create_vector_layer(self, geom_type, prj_code): """ Creates/resets the internal vector layer that will be used to draw the spatial unit overlays. :param geom_type: Geometry type :type geom_type: str :param prj_code: EPSG code :type prj_code: int """ self._overlay_layer = QgsVectorLayer( u"{0}?crs=epsg:{1!s}&field=lbname:string(20)&index=yes".format(geom_type, prj_code), "view_str_spatial_unit", "memory") def draw_spatial_unit(self, spatial_unit, model): """ Draw geometry of the given model in the respective local and web views. :param model: Source model whose geometry will be drawn. :type model: object :param clear_existing: Clears any existing features prior to adding the new features. :type clear_existing: bool """ if model is None: msg = QApplication.translate("SpatialPreview", "Data model is empty, the spatial " "unit cannot be rendered.") QMessageBox.critical(self, QApplication.translate( "SpatialPreview", "Spatial Unit Preview"), msg) return table_name = spatial_unit.name if not pg_table_exists(table_name): msg = QApplication.translate("SpatialPreview", "The spatial unit data source could " "not be retrieved, the feature cannot " "be rendered.") QMessageBox.critical( self, QApplication.translate( "SpatialPreview", "Spatial Unit Preview"), msg ) return sp_unit_manager = SpatialUnitManagerDockWidget(self.iface()) spatial_cols = sp_unit_manager.geom_columns(spatial_unit) geom, geom_col = None, "" sc_obj = None for sc in spatial_cols: db_geom = getattr(model, sc.name) #Use the first non-empty geometry # value in the collection if not db_geom is None: sc_obj = sc geom_col = sc.name geom = db_geom QApplication.processEvents() lyr = sp_unit_manager.geom_col_layer_name( table_name, sc_obj ) sp_unit_manager.add_layer_by_name(lyr) if geom is not None: self.highlight_spatial_unit( spatial_unit, geom, self.local_map.canvas ) self._web_spatial_loader.add_overlay( model, geom_col ) def clear_sel_highlight(self): """ Removes sel_highlight from the canvas. :return: """ if self.sel_highlight is not None: self.sel_highlight = None def get_layer_source(self, layer): """ Get the layer table name if the source is from the database. :param layer: The layer for which the source is checked :type QGIS vectorlayer :return: String or None """ source = layer.source() vals = dict(re.findall('(\S+)="?(.*?)"? ', source)) try: table = vals['table'].split('.') table_name = table[1].strip('"') return table_name except KeyError: return None def spatial_unit_layer(self, spatial_unit, active_layer): """ Check whether the layer is parcel layer or not. :param active_layer: The layer to be checked :type QGIS vectorlayer :return: Boolean """ if self.active_layer_check(): layers = self.iface().legendInterface().layers() for layer in layers: layer_source = self.get_layer_source(layer) if layer_source == spatial_unit.name: self.iface().setActiveLayer(layer) return True not_sp_msg = QApplication.translate( 'SpatialPreview', 'You have selected a non-spatial_unit layer. ' 'Please select a spatial unit layer to preview.' ) QMessageBox.information( self._iface.mainWindow(), "Error", not_sp_msg ) def active_layer_check(self): """ Check if there is active layer and if not, displays a message box to select a parcel layer. :return: """ active_layer = self._iface.activeLayer() if active_layer is None: no_layer_msg = QApplication.translate( 'SpatialPreview', 'Please add a spatial unit layer ' 'to preview the spatial unit.' ) QMessageBox.critical( self._iface.mainWindow(), "Error", no_layer_msg ) return False else: return True def _add_geom_to_map(self, geom): if self._overlay_layer is None: return geom_func = geom.ST_AsText() geom_wkt = self._db_session.scalar(geom_func) dp = self._overlay_layer.dataProvider() feat = QgsFeature() qgis_geom = QgsGeometry.fromWkt(geom_wkt) feat.setGeometry(g) dp.addFeatures([feat]) self._overlay_layer.updateExtents() return qgis_geom.boundingBox() def highlight_spatial_unit( self, spatial_unit, geom, map_canvas ): layer = self._iface.activeLayer() map_canvas.setExtent(layer.extent()) map_canvas.refresh() if self.spatial_unit_layer(spatial_unit, layer): self.clear_sel_highlight() qgis_geom = qgsgeometry_from_wkbelement(geom) self.sel_highlight = QgsHighlight( map_canvas, qgis_geom, layer ) rgba = selection_color() self.sel_highlight.setFillColor(rgba) self.sel_highlight.setWidth(3) self.sel_highlight.show() extent = qgis_geom.boundingBox() extent.scale(1.5) map_canvas.setExtent(extent) map_canvas.refresh() else: return def remove_preview_layer(self, layer, name): """ Removes the preview layer from legend. :param layer: The preview polygon layer to be removed. :param name: The name of the layer to be removed. :return: None """ if layer is not None: for lyr in QgsMapLayerRegistry.instance().mapLayers().values(): if lyr.name() == name: id = lyr.id() QgsMapLayerRegistry.instance().removeMapLayer(id) def delete_local_features(self, feature_ids=[]): """ Removes features in the local map overlay. """ del_status = False if not self._overlay_layer is None: if len(feature_ids) == 0: feature_ids = self._overlay_layer.allFeatureIds() del_status = self._overlay_layer.dataProvider().deleteFeatures(feature_ids) return del_status def remove_layer(self): """ Removes both the local and web layers. """ if not self._overlay_layer is None: QgsProject.instance().layerTreeRoot().removeLayer(self._overlay_layer) #Clear web overlays self._web_spatial_loader.removeOverlay() self._overlay_layer = None def _on_overlay_to_be_removed(self, layers_ids): """ Resets the local layer variable and removes the web overlay. """ if not self._overlay_layer is None: if self._overlay_layer.id() in layers_ids: self.remove_layer() def on_spatial_browser_error(self, err): """ Slot raised when an error occurs when loading items in the property browser """ self._insert_notification(err, ERROR) def on_spatial_browser_loading(self, progress): """ Slot raised when the property browser is loading. Displays the progress of the page loading as a percentage. """ if progress <= 0 or progress >= 100: self.lblInfo.setText("") self.lblInfo.setVisible(False) else: self.lblInfo.setVisible(True) self.lblInfo.setText("Loading...%d%%)"%(progress)) def on_spatial_browser_finished(self, status): """ Slot raised when the property browser finishes loading the content """ if status: if len(self.local_map.canvas_layers()) > 0:# and not self._ol_loaded: self.on_sync_extents() self._ol_loaded = True #self._overlay_spatial_unit() else: msg = QApplication.translate("SpatialPreview", "Error: Spatial unit cannot be loaded.") self._insert_notification(msg, ERROR) def on_zoom_changed(self): """ Slot raised when the zoom value in the slider changes. This is only raised once the user releases the slider with the mouse. """ zoom = self.zoomSlider.value() self._web_spatial_loader.zoom_to_level(zoom) def on_load_GMaps(self, state): """ Slot raised when a user clicks to set Google Maps Satellite as the base layer """ if state: self._web_spatial_loader.setBaseLayer(GMAP_SATELLITE) def on_load_OSM(self, state): """ Slot raised when a user clicks to set OSM as the base layer """ if state: self._web_spatial_loader.setBaseLayer(OSM) def on_map_zoom_level_changed(self, level): """ Slot which is raised when the zoom level of the map changes. """ self.zoomSlider.setValue(level) def on_reset_web_map(self): """ Slot raised when the user clicks to reset the property location in the map. """ self._web_spatial_loader.zoom_to_extents() def on_sync_extents(self): """ Slot raised to synchronize the webview extents with those of the local map canvas. """ if len(self.local_map.canvas_layers()) > 0:# and self._ol_loaded: curr_extent = self.map_extents() self._web_spatial_loader.zoom_to_map_extents(curr_extent) def map_extents(self): """ :returns: Current extents of the local map. :rtype: QgsRectangle """ return self.local_map.extent() def canvas_zoom_to_extent(self, extent): self.local_map.canvas.setExtent(extent)
def getFlash(): h = QgsHighlight( self.canvas, geometry, layer ) h.setColor( QColor( 255, 0, 0, 255 ) ) h.setFillColor( QColor( 255, 0, 0, 100 ) ) h.setWidth( 2 ) return h
class SpatialPreview(QTabWidget, Ui_frmPropertyPreview): """ Widget for previewing spatial unit on either local map or web source. """ def __init__(self, parent=None, iface=None): QTabWidget.__init__(self, parent) self.setupUi(self) self._notif_bar = None self._ol_loaded = False self._overlay_layer = None self.sel_highlight = None self.memory_layer = None self._db_session = STDMDb.instance().session self.set_iface(iface) #Web config self._web_spatial_loader = WebSpatialLoader(self.spatial_web_view, self) #Connect signals self._web_spatial_loader.loadError.connect( self.on_spatial_browser_error) self._web_spatial_loader.loadProgress.connect( self.on_spatial_browser_loading) self._web_spatial_loader.loadFinished.connect( self.on_spatial_browser_finished) self._web_spatial_loader.zoomChanged.connect( self.on_map_zoom_level_changed) self.rbGMaps.toggled.connect(self.on_load_GMaps) self.rbOSM.toggled.connect(self.on_load_OSM) self.zoomSlider.sliderReleased.connect(self.on_zoom_changed) self.btnResetMap.clicked.connect(self.on_reset_web_map) self.btnSync.clicked.connect(self.on_sync_extents) QgsMapLayerRegistry.instance().layersWillBeRemoved.connect( self._on_overlay_to_be_removed) def set_iface(self, iface): self._iface = iface self.local_map.set_iface(iface) def set_notification_bar(self, notif_bar): """ Set notification widget. :param notif_bar: Notification widget. """ self._notif_bar = notif_bar def notification_bar(self): """ :return: Currently configured notification bar. """ return self._notif_bar def _insert_notification(self, msg, level, clear_first=True): if self._notif_bar is None: return if clear_first: self._notif_bar.clear() self._notif_bar.insertNotification(msg, level) def iface(self): return self._iface def _setDefaults(self): """ Set default settings """ self.set_canvas_background_color(self.canvasBgColor) def set_canvas_background_color(self, color): """ Set the background color of the map canvas """ self.localMap.setCanvasColor(color) self.canvasBgColor = color def refresh_canvas_layers(self, process_events=False): """ Reload map layers in the viewer canvas. """ self.local_map.refresh_layers() def load_web_map(self): """ Loads the web map into the view using canvas extents if there are existing layers in the map canvas. """ if not self._ol_loaded: self._web_spatial_loader.load() def _create_vector_layer(self, geom_type, prj_code): """ Creates/resets the internal vector layer that will be used to draw the spatial unit overlays. :param geom_type: Geometry type :type geom_type: str :param prj_code: EPSG code :type prj_code: int """ self._overlay_layer = QgsVectorLayer( u"{0}?crs=epsg:{1!s}&field=lbname:string(20)&index=yes".format( geom_type, prj_code), "view_str_spatial_unit", "memory") def draw_spatial_unit(self, spatial_unit, model): """ Draw geometry of the given model in the respective local and web views. :param model: Source model whose geometry will be drawn. :type model: object :param clear_existing: Clears any existing features prior to adding the new features. :type clear_existing: bool """ if model is None: msg = QApplication.translate( "SpatialPreview", "Data model is empty, the spatial " "unit cannot be rendered.") QMessageBox.critical( self, QApplication.translate("SpatialPreview", "Spatial Unit Preview"), msg) return table_name = spatial_unit.name if not pg_table_exists(table_name): msg = QApplication.translate( "SpatialPreview", "The spatial unit data source could " "not be retrieved, the feature cannot " "be rendered.") QMessageBox.critical( self, QApplication.translate("SpatialPreview", "Spatial Unit Preview"), msg) return sp_unit_manager = SpatialUnitManagerDockWidget(self.iface()) spatial_cols = sp_unit_manager.geom_columns(spatial_unit) geom, geom_col = None, "" sc_obj = None for sc in spatial_cols: db_geom = getattr(model, sc.name) #Use the first non-empty geometry # value in the collection if not db_geom is None: sc_obj = sc geom_col = sc.name geom = db_geom QApplication.processEvents() lyr = sp_unit_manager.geom_col_layer_name(table_name, sc_obj) sp_unit_manager.add_layer_by_name(lyr) if geom is not None: self.highlight_spatial_unit(spatial_unit, geom, self.local_map.canvas) self._web_spatial_loader.add_overlay(model, geom_col) def clear_sel_highlight(self): """ Removes sel_highlight from the canvas. :return: """ if self.sel_highlight is not None: self.sel_highlight = None def get_layer_source(self, layer): """ Get the layer table name if the source is from the database. :param layer: The layer for which the source is checked :type QGIS vectorlayer :return: String or None """ source = layer.source() vals = dict(re.findall('(\S+)="?(.*?)"? ', source)) try: table = vals['table'].split('.') table_name = table[1].strip('"') return table_name except KeyError: return None def spatial_unit_layer(self, spatial_unit, active_layer): """ Check whether the layer is parcel layer or not. :param active_layer: The layer to be checked :type QGIS vectorlayer :return: Boolean """ if self.active_layer_check(): layers = self.iface().legendInterface().layers() for layer in layers: layer_source = self.get_layer_source(layer) if layer_source == spatial_unit.name: self.iface().setActiveLayer(layer) return True not_sp_msg = QApplication.translate( 'SpatialPreview', 'You have selected a non-spatial_unit layer. ' 'Please select a spatial unit layer to preview.') QMessageBox.information(self._iface.mainWindow(), "Error", not_sp_msg) def active_layer_check(self): """ Check if there is active layer and if not, displays a message box to select a parcel layer. :return: """ active_layer = self._iface.activeLayer() if active_layer is None: no_layer_msg = QApplication.translate( 'SpatialPreview', 'Please add a spatial unit layer ' 'to preview the spatial unit.') QMessageBox.critical(self._iface.mainWindow(), "Error", no_layer_msg) return False else: return True def _add_geom_to_map(self, geom): if self._overlay_layer is None: return geom_func = geom.ST_AsText() geom_wkt = self._db_session.scalar(geom_func) dp = self._overlay_layer.dataProvider() feat = QgsFeature() qgis_geom = QgsGeometry.fromWkt(geom_wkt) feat.setGeometry(g) dp.addFeatures([feat]) self._overlay_layer.updateExtents() return qgis_geom.boundingBox() def highlight_spatial_unit(self, spatial_unit, geom, map_canvas): layer = self._iface.activeLayer() map_canvas.setExtent(layer.extent()) map_canvas.refresh() if self.spatial_unit_layer(spatial_unit, layer): self.clear_sel_highlight() qgis_geom = qgsgeometry_from_wkbelement(geom) self.sel_highlight = QgsHighlight(map_canvas, qgis_geom, layer) rgba = selection_color() self.sel_highlight.setFillColor(rgba) self.sel_highlight.setWidth(3) self.sel_highlight.show() extent = qgis_geom.boundingBox() extent.scale(1.5) map_canvas.setExtent(extent) map_canvas.refresh() else: return def remove_preview_layer(self, layer, name): """ Removes the preview layer from legend. :param layer: The preview polygon layer to be removed. :param name: The name of the layer to be removed. :return: None """ if layer is not None: for lyr in QgsMapLayerRegistry.instance().mapLayers().values(): if lyr.name() == name: id = lyr.id() QgsMapLayerRegistry.instance().removeMapLayer(id) def delete_local_features(self, feature_ids=[]): """ Removes features in the local map overlay. """ del_status = False if not self._overlay_layer is None: if len(feature_ids) == 0: feature_ids = self._overlay_layer.allFeatureIds() del_status = self._overlay_layer.dataProvider().deleteFeatures( feature_ids) return del_status def remove_layer(self): """ Removes both the local and web layers. """ if not self._overlay_layer is None: QgsProject.instance().layerTreeRoot().removeLayer( self._overlay_layer) #Clear web overlays self._web_spatial_loader.removeOverlay() self._overlay_layer = None def _on_overlay_to_be_removed(self, layers_ids): """ Resets the local layer variable and removes the web overlay. """ if not self._overlay_layer is None: if self._overlay_layer.id() in layers_ids: self.remove_layer() def on_spatial_browser_error(self, err): """ Slot raised when an error occurs when loading items in the property browser """ self._insert_notification(err, ERROR) def on_spatial_browser_loading(self, progress): """ Slot raised when the property browser is loading. Displays the progress of the page loading as a percentage. """ if progress <= 0 or progress >= 100: self.lblInfo.setText("") self.lblInfo.setVisible(False) else: self.lblInfo.setVisible(True) self.lblInfo.setText("Loading...%d%%)" % (progress)) def on_spatial_browser_finished(self, status): """ Slot raised when the property browser finishes loading the content """ if status: if len(self.local_map.canvas_layers() ) > 0: # and not self._ol_loaded: self.on_sync_extents() self._ol_loaded = True #self._overlay_spatial_unit() else: msg = QApplication.translate( "SpatialPreview", "Error: Spatial unit cannot be loaded.") self._insert_notification(msg, ERROR) def on_zoom_changed(self): """ Slot raised when the zoom value in the slider changes. This is only raised once the user releases the slider with the mouse. """ zoom = self.zoomSlider.value() self._web_spatial_loader.zoom_to_level(zoom) def on_load_GMaps(self, state): """ Slot raised when a user clicks to set Google Maps Satellite as the base layer """ if state: self._web_spatial_loader.setBaseLayer(GMAP_SATELLITE) def on_load_OSM(self, state): """ Slot raised when a user clicks to set OSM as the base layer """ if state: self._web_spatial_loader.setBaseLayer(OSM) def on_map_zoom_level_changed(self, level): """ Slot which is raised when the zoom level of the map changes. """ self.zoomSlider.setValue(level) def on_reset_web_map(self): """ Slot raised when the user clicks to reset the property location in the map. """ self._web_spatial_loader.zoom_to_extents() def on_sync_extents(self): """ Slot raised to synchronize the webview extents with those of the local map canvas. """ if len(self.local_map.canvas_layers()) > 0: # and self._ol_loaded: curr_extent = self.map_extents() self._web_spatial_loader.zoom_to_map_extents(curr_extent) def map_extents(self): """ :returns: Current extents of the local map. :rtype: QgsRectangle """ return self.local_map.extent() def canvas_zoom_to_extent(self, extent): self.local_map.canvas.setExtent(extent)
class LinkerDock(QDockWidget, Ui_linker, SettingDialog): def __init__(self, iface): # QGIS self.iface = iface self.settings = MySettings() self.linkRubber = QgsRubberBand(self.iface.mapCanvas()) self.featureHighlight = None # Relation management self.relationManager = QgsProject.instance().relationManager() self.relationManager.changed.connect(self.loadRelations) self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationWidgetWrapper = None self.editorContext = QgsAttributeEditorContext() self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools()) # GUI QDockWidget.__init__(self) self.setupUi(self) SettingDialog.__init__(self, MySettings(), False, True) self.drawButton.setChecked(self.settings.value("drawEnabled")) self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas()) self.mapTool.setButton(self.identifyReferencingFeatureButton) # Connect signal/slot self.relationComboBox.currentIndexChanged.connect( self.currentRelationChanged) self.mapTool.featureIdentified.connect(self.setReferencingFeature) # load relations at start self.loadRelations() def showEvent(self, QShowEvent): self.drawLink() def closeEvent(self, e): self.iface.mapCanvas().unsetMapTool(self.mapTool) self.linkRubber.reset() self.deleteHighlight() self.deleteWrapper() self.disconnectLayer() def disconnectLayer(self): if self.relation.isValid(): self.relation.referencingLayer().editingStarted.disconnect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect( self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect( self.layerValueChangedOutside) def runForFeature(self, relationId, layer, feature): index = self.relationComboBox.findData(relationId) self.relationComboBox.setCurrentIndex(index) self.setReferencingFeature(feature) self.show() if not layer.isEditable(): self.iface.messageBar().pushMessage( "Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4) else: self.relationReferenceWidget.mapIdentification() @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked") def activateMapTool(self): self.iface.mapCanvas().setMapTool(self.mapTool) def deactivateMapTool(self): self.iface.mapCanvas().unsetMapTool(self.mapTool) def loadRelations(self): self.deleteWrapper() self.disconnectLayer() self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationComboBox.currentIndexChanged.disconnect( self.currentRelationChanged) self.relationComboBox.clear() for relation in self.relationManager.referencedRelations(): if relation.referencingLayer().hasGeometryType(): self.relationComboBox.addItem(relation.name(), relation.id()) self.relationComboBox.setCurrentIndex(-1) self.relationComboBox.currentIndexChanged.connect( self.currentRelationChanged) self.currentRelationChanged(-1) def currentRelationChanged(self, index): # disconnect previous relation if self.relation.isValid(): try: self.relation.referencingLayer().editingStarted.disconnect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect( self.relationEditableChanged) self.relation.referencingLayer( ).attributeValueChanged.disconnect( self.layerValueChangedOutside) except TypeError: pass self.referencingFeatureLayout.setEnabled(index >= 0) relationId = self.relationComboBox.itemData(index) self.relation = self.relationManager.relation(relationId) self.mapTool.setLayer(self.relation.referencingLayer()) self.setReferencingFeature() # connect if self.relation.isValid(): self.relation.referencingLayer().editingStarted.connect( self.relationEditableChanged) self.relation.referencingLayer().editingStopped.connect( self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.connect( self.layerValueChangedOutside) def setReferencingFeature(self, feature=QgsFeature()): self.deactivateMapTool() self.referencingFeature = QgsFeature(feature) self.deleteWrapper() # disable relation reference widget if no referencing feature self.referencedFeatureLayout.setEnabled(feature.isValid()) # set line edit if not self.relation.isValid() or not feature.isValid(): self.referencingFeatureLineEdit.clear() return self.referencingFeatureLineEdit.setText("%s" % feature.id()) fieldIdx = self.referencingFieldIndex() widgetConfig = self.relation.referencingLayer().editorWidgetV2Config( fieldIdx) self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create( "RelationReference", self.relation.referencingLayer(), fieldIdx, widgetConfig, self.relationReferenceWidget, self, self.editorContext) self.relationWidgetWrapper.setEnabled( self.relation.referencingLayer().isEditable()) self.relationWidgetWrapper.setValue(feature[fieldIdx]) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) # override field definition to allow map identification self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) # update drawn link self.highlightReferencingFeature() self.drawLink() def deleteWrapper(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.valueChanged.disconnect( self.foreignKeyChanged) self.relationWidgetWrapper.setValue(None) del self.relationWidgetWrapper self.relationWidgetWrapper = None def foreignKeyChanged(self, newKey): if not self.relation.isValid() or not self.relation.referencingLayer( ).isEditable() or not self.referencingFeature.isValid(): self.drawLink() return if not self.relation.referencingLayer().editBuffer( ).changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey): self.iface.messageBar().pushMessage( "Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL) self.drawLink() def relationEditableChanged(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.setEnabled( self.relation.isValid() and self.relation.referencingLayer().isEditable()) def layerValueChangedOutside(self, fid, fieldIdx, value): if not self.relation.isValid() or not self.referencingFeature.isValid( ) or self.relationWidgetWrapper is None: return # not the correct feature if fid != self.referencingFeature.id(): return # not the correct field if fieldIdx != self.referencingFieldIndex(): return # widget already has this value if value == self.relationWidgetWrapper.value(): return self.relationWidgetWrapper.valueChanged.disconnect( self.foreignKeyChanged) self.relationWidgetWrapper.setValue(value) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) def referencingFieldIndex(self): if not self.relation.isValid(): return -1 fieldName = self.relation.fieldPairs().keys()[0] fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName) return fieldIdx @pyqtSlot(bool, name="on_drawButton_toggled") def drawLink(self): self.settings.setValue("drawEnabled", self.drawButton.isChecked()) self.linkRubber.reset() if not self.drawButton.isChecked( ) or not self.referencingFeature.isValid( ) or not self.relation.isValid(): return referencedFeature = self.relationReferenceWidget.referencedFeature() if not referencedFeature.isValid(): return p1 = self.centroid(self.relation.referencedLayer(), referencedFeature) p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature) geom = arc(p1, p2) self.linkRubber.setToGeometry(geom, None) self.linkRubber.setWidth(self.settings.value("rubberWidth")) self.linkRubber.setColor(self.settings.value("rubberColor")) self.linkRubber.setLineStyle(Qt.DashLine) def centroid(self, layer, feature): geom = feature.geometry() if geom.type() == QGis.Line: geom = geom.interpolate(geom.length() / 2) else: geom = geom.centroid() return self.iface.mapCanvas().mapSettings().layerToMapCoordinates( layer, geom.asPoint()) @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked") def highlightReferencingFeature(self): self.deleteHighlight() if not self.relation.isValid() or not self.referencingFeature.isValid( ): return self.featureHighlight = QgsHighlight( self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer()) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int( settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) bbuffer = float( settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) minWidth = float( settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.featureHighlight.setColor(color) color.setAlpha(alpha) self.featureHighlight.setFillColor(color) self.featureHighlight.setBuffer(bbuffer) self.featureHighlight.setMinWidth(minWidth) self.featureHighlight.show() timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.deleteHighlight) timer.start(3000) def deleteHighlight(self): if self.featureHighlight: del self.featureHighlight self.featureHighlight = None
class FeatureSelectorWidget(QWidget): featureIdentified = pyqtSignal(QgsFeature) def __init__(self, parent): QWidget.__init__(self, parent) editLayout = QHBoxLayout() editLayout.setContentsMargins(0, 0, 0, 0) editLayout.setSpacing(2) self.setLayout(editLayout) self.lineEdit = QLineEdit(self) self.lineEdit.setReadOnly(True) editLayout.addWidget(self.lineEdit) self.highlightFeatureButton = QToolButton(self) self.highlightFeatureButton.setPopupMode(QToolButton.MenuButtonPopup) self.highlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"), "Highlight feature", self) self.scaleHighlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"), "Scale and highlight feature", self) self.panHighlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"), "Pan and highlight feature", self) self.highlightFeatureButton.addAction(self.highlightFeatureAction) self.highlightFeatureButton.addAction(self.scaleHighlightFeatureAction) self.highlightFeatureButton.addAction(self.panHighlightFeatureAction) self.highlightFeatureButton.setDefaultAction(self.highlightFeatureAction) editLayout.addWidget(self.highlightFeatureButton) self.mapIdentificationButton = QToolButton(self) self.mapIdentificationButton.setIcon(QgsApplication.getThemeIcon("/mActionMapIdentification.svg")) self.mapIdentificationButton.setText("Select on map") self.mapIdentificationButton.setCheckable(True) editLayout.addWidget(self.mapIdentificationButton) self.mapIdentificationButton.clicked.connect(self.mapIdentification) self.highlightFeatureButton.triggered.connect(self.highlightActionTriggered) self.layer = None self.mapTool = None self.canvas = None self.windowWidget = None self.highlight = None self.feature = QgsFeature() def setCanvas(self, mapCanvas): self.mapTool = QgsMapToolIdentifyFeature(mapCanvas) self.mapTool.setButton(self.mapIdentificationButton) self.canvas = mapCanvas def setLayer(self, layer): self.layer = layer def setFeature(self, feature, canvasExtent = CanvasExtent.Fixed): self.lineEdit.clear() self.feature = feature if not self.feature.isValid() or self.layer is None: return featureTitle = feature.attribute(self.layer.displayField()) if featureTitle == '': featureTitle = feature.id() self.lineEdit.setText(str(featureTitle)) self.highlightFeature(canvasExtent) def clear(self): self.feature = QgsFeature() self.lineEdit.clear() def mapIdentification(self): if self.layer is None or self.mapTool is None or self.canvas is None: return self.mapTool.setLayer(self.layer) self.canvas.setMapTool(self.mapTool) self.windowWidget = QWidget.window(self) self.canvas.window().raise_() self.canvas.activateWindow() self.canvas.setFocus() self.mapTool.featureIdentified.connect(self.mapToolFeatureIdentified) self.mapTool.deactivated.connect(self.mapToolDeactivated) def mapToolFeatureIdentified(self, feature): feature = QgsFeature(feature) self.featureIdentified.emit(feature) self.unsetMapTool() self.setFeature(feature) def mapToolDeactivated(self): if self.windowWidget is not None: self.windowWidget.raise_() self.windowWidget.activateWindow() def highlightFeature(self, canvasExtent = CanvasExtent.Fixed): if self.canvas is None or not self.feature.isValid(): return geom = self.feature.geometry() if geom is None: return if canvasExtent == CanvasExtent.Scale: featBBox = geom.boundingBox() featBBox = self.canvas.mapSettings().layerToMapCoordinates(self.layer, featBBox) extent = self.canvas.extent() if not extent.contains(featBBox): extent.combineExtentWith(featBBox) extent.scale(1.1) self.canvas.setExtent(extent) self.canvas.refresh() elif canvasExtent == CanvasExtent.Pan: centroid = geom.centroid() center = centroid.asPoint() center = self.canvas.mapSettings().layerToMapCoordinates(self.layer, center) self.canvas.zoomByFactor(1.0, center) # refresh is done in this method # highlight self.delete_highlight() self.highlight = QgsHighlight(self.canvas, geom, self.layer) settings = QSettings() color = QColor(settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) buffer = 2*float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) min_width = 2*float(settings.value("/Map/highlight/min_width", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.highlight.setColor(color) # sets also fill with default alpha color.setAlpha(alpha) self.highlight.setFillColor(color) # sets fill with alpha self.highlight.setBuffer(buffer) self.highlight.setMinWidth(min_width) self.highlight.setWidth(4.0) self.highlight.show() self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(self.delete_highlight) self.timer.start(3000) def delete_highlight(self): if self.highlight is not None: self.highlight.hide() del self.highlight self.highlight = None def unsetMapTool(self): if self.canvas is not None and self.mapTool is not None: # this will call mapToolDeactivated self.canvas.unsetMapTool(self.mapTool) def highlightActionTriggered(self, action): self.highlightFeatureButton.setDefaultAction(action) if action == self.highlightFeatureAction: self.highlightFeature() elif action == self.scaleHighlightFeatureAction: self.highlightFeature(CanvasExtent.Scale) elif action == self.panHighlightFeatureAction: self.highlightFeature(CanvasExtent.Pan)
class DetailsTreeView(DetailsDBHandler, DetailsDockWidget): def __init__(self, iface, spatial_unit_dock): """ The method initializes the dockwidget. :param iface: QGIS user interface class :type class qgis.utils.iface :param plugin: The STDM plugin :type class :return: None """ from stdm.ui.entity_browser import _EntityDocumentViewerHandler DetailsDockWidget.__init__(self, iface, spatial_unit_dock) DetailsDBHandler.__init__(self) self.spatial_unit_dock = spatial_unit_dock self.view = QTreeView() self.view.setSelectionBehavior( QAbstractItemView.SelectRows ) #self.feature_ids = [] self.layer_table = None self.entity = None self.feature_models = {} self.party_models = {} self.STR_models = {} self.feature_STR_model = {} self.removed_feature = None self.selected_root = None self.model = QStandardItemModel() self.view.setModel(self.model) self.view.setUniformRowHeights(True) self.view.setRootIsDecorated(True) self.view.setAlternatingRowColors(True) self.view.setWordWrap(True) self.view.setHeaderHidden(True) self.view.setEditTriggers( QAbstractItemView.NoEditTriggers ) self.current_profile = current_profile() self.social_tenure = self.current_profile.social_tenure self.spatial_unit = self.social_tenure.spatial_unit self.party = self.social_tenure.party self.view.setMinimumWidth(250) self.doc_viewer_title = QApplication.translate( 'EntityBrowser', 'Document Viewer' ) self.doc_viewer = _EntityDocumentViewerHandler( self.doc_viewer_title, self.iface.mainWindow() ) def set_layer_entity(self): self.layer_table = self.get_layer_source( self.iface.activeLayer() ) if self.layer_table in spatial_tables(): self.entity = self.current_profile.entity_by_name( self.layer_table ) else: self.treeview_error('The layer is not a spatial entity layer. ') def activate_feature_details(self, button_clicked=True): """ Action for showing feature details. :return: """ # Get the active layer. active_layer = self.iface.activeLayer() # TODO fix feature_details_btn is deleted error. if active_layer is not None and \ self.spatial_unit_dock.feature_details_btn.isChecked(): # if feature detail dock is not defined or hidden, create empty dock. if self is None or self.isHidden(): # if the selected layer is not a feature layer, show not # feature layer. (implicitly included in the if statement). if not self.feature_layer(active_layer): self.spatial_unit_dock.feature_details_btn.setChecked(False) return # If the selected layer is feature layer, get data and # display treeview in a dock widget else: select_feature = QApplication.translate( "STDMQGISLoader", "Please select a feature to view their details." ) self.init_dock() self.add_tree_view() self.model.clear() self.treeview_error(select_feature) # enable the select tool self.activate_select_tool() # set entity from active layer in the child class self.set_layer_entity() # set entity for the super class DetailModel self.set_entity(self.entity) # Registery column widget self.set_formatter() #set formatter for social tenure relationship. self.set_formatter(self.social_tenure) self.set_formatter(self.party) # pull data, show treeview active_layer.selectionChanged.connect( self.show_tree ) self.steam_signals(self.entity) # if feature_detail dock is open, toggle close else: self.close_dock( self.spatial_unit_dock.feature_details_btn ) self.feature_details = None # if no active layer, show error message and uncheck the feature tool else: if button_clicked: self.active_layer_check() self.spatial_unit_dock.feature_details_btn.setChecked(False) def add_tree_view(self): """ Adds tree view to the dock widget and sets style. :return: None """ self.tree_scrollArea.setWidget(self.view) def clear_feature_models(self): self.feature_models.clear() def reset_tree_view(self, no_feature=False): #clear feature_ids list, model and highlight self.model.clear() self.clear_sel_highlight() # remove sel_highlight self.disable_buttons(no_feature) if self.removed_feature is None: self.STR_models.clear() self.feature_models.clear() else: self.removed_feature = None features = self.selected_features() # if the selected feature is over 1, # activate multi_select_highlight if not features is None: self.view.clicked.connect( self.multi_select_highlight ) # if there is at least one selected feature if len(features) > 0: self.add_tree_view() #self.feature_ids = features def disable_buttons(self, bool): self.edit_btn.setDisabled(bool) self.delete_btn.setDisabled(bool) def show_tree(self): selected_features = self.selected_features() if len(selected_features) < 1: self.reset_tree_view(True) return if not self.entity is None: self.reset_tree_view() if len(selected_features) < 1: self.disable_buttons(True) return layer_icon = QIcon(':/plugins/stdm/images/icons/layer.gif') roots = self.add_parent_tree( layer_icon, format_name(self.entity.short_name) ) if roots is None: return for id, root in roots.iteritems(): db_model = entity_id_to_model(self.entity, id) self.add_roots(db_model, root, id) def add_parent_tree(self, icon, title): roots = OrderedDict() for feature_id in self.selected_features(): root = QStandardItem(icon, title) root.setData(feature_id) self.set_bold(root) self.model.appendRow(root) roots[feature_id] = root return roots def add_roots(self, model, parent, feature_id): self.feature_models[feature_id] = model if model is None: return self.column_widget_registry(model, self.entity) for i, (col, row) in enumerate(self.formatted_record.iteritems()): child = QStandardItem('{}: {}'.format(col, row)) child.setSelectable(False) parent.appendRow([child]) # Add Social Tenure Relationship steam as a last child if i == len(self.formatted_record)-1: self.add_STR_child(parent, feature_id) self.expand_node(parent) def add_STR_steam(self, parent, STR_id): str_icon = QIcon( ':/plugins/stdm/images/icons/social_tenure.png' ) title = 'Social Tenure Relationship' str_root = QStandardItem(str_icon, title) str_root.setData(STR_id) self.set_bold(str_root) parent.appendRow([str_root]) return str_root def add_no_STR_steam(self, parent): if self.entity.name == self.spatial_unit.name: no_str_icon = QIcon( ':/plugins/stdm/images/icons/remove.png' ) title = 'No STR Defined' no_str_root = QStandardItem(no_str_icon, title) self.set_bold(no_str_root) parent.appendRow([no_str_root]) def add_STR_child(self, parent, feature_id): if len(self.feature_STR_link(feature_id)) < 1: self.add_no_STR_steam(parent) return for record in self.feature_STR_link(feature_id): self.STR_models[record.id] = record str_root = self.add_STR_steam(parent, record.id) # add STR children self.column_widget_registry(record, self.social_tenure) for i, (col, row) in enumerate( self.formatted_record.iteritems() ): STR_child = QStandardItem( '{}: {}'.format(col, row) ) STR_child.setSelectable(False) str_root.appendRow([STR_child]) if i == len(self.formatted_record)-1: self.add_party_child( str_root, record.party_id ) self.feature_STR_model[feature_id] = self.STR_models.keys() def add_party_steam(self, parent, party_id): party_icon = QIcon( ':/plugins/stdm/images/icons/table.png' ) title = format_name(self.party.short_name) party_root = QStandardItem(party_icon, title) party_root.setData(party_id) self.set_bold(party_root) parent.appendRow([party_root]) party_root.setEditable(False) return party_root def add_party_child(self, parent, party_id): db_model = entity_id_to_model(self.party, party_id) self.party_models[party_id] = db_model party_root = self.add_party_steam(parent, party_id) # add STR children self.column_widget_registry(db_model, self.party) for col, row in self.formatted_record.iteritems(): party_child = QStandardItem('{}: {}'.format(col, row)) party_child.setSelectable(False) party_root.appendRow([party_child]) def set_bold(self, standard_item): """ Make a text of Qstandaritem to bold. :param standard_item: Qstandaritem :type: Qstandaritem :return: None """ font = standard_item.font() font.setBold(True) standard_item.setFont(font) def treeview_error(self, message, icon=None): """ Displays error message in feature details treeview. :param title: the title of the treeview. :type: String :param message: The message to be displayed. :type: String :param icon: The icon of the item. :type: Resource string :return: None """ not_feature_ft_msg = QApplication.translate( 'FeatureDetails', message ) if icon== None: root = QStandardItem(not_feature_ft_msg) else: root = QStandardItem(icon, not_feature_ft_msg) self.view.setRootIsDecorated(False) self.model.appendRow(root) self.view.setRootIsDecorated(True) def expand_node(self, parent): """ Make the last tree node expand. :param parent: The parent to expand :type QStandardItem :return:None """ index = self.model.indexFromItem(parent) self.view.expand(index) def multi_select_highlight(self, index): """ Highlights a feature with rubberBald class when selecting features are more than one. :param index: Selected QTreeView item index :type Integer :return: None """ map = self.iface.mapCanvas() try: # Get the selected item text using the index selected_item = self.model.itemFromIndex(index) # Use mutli-select only when more than 1 items are selected. if self.layer.selectedFeatures() < 2: return self.selected_root = selected_item # Split the text to get the key and value. selected_item_text = selected_item.text() selected_value = selected_item.data() # If the first word is feature, expand & highlight. if selected_item_text == format_name(self.spatial_unit.short_name): self.view.expand(index) # expand the item # Clear any existing highlight self.clear_sel_highlight() # Insert highlight # Create expression to target the selected feature expression = QgsExpression( "\"id\"='" + str(selected_value) + "'" ) # Get feature iteration based on the expression ft_iteration = self.layer.getFeatures( QgsFeatureRequest(expression) ) # Retrieve geometry and attributes for feature in ft_iteration: # Fetch geometry geom = feature.geometry() self.sel_highlight = QgsHighlight(map, geom, self.layer) self.sel_highlight.setFillColor(selection_color()) self.sel_highlight.setWidth(4) self.sel_highlight.setColor(QColor(212,95,0, 255)) self.sel_highlight.show() break except AttributeError: # pass attribute error on child items such as party pass except IndexError: pass def steam_signals(self, entity): self.edit_btn.clicked.connect( lambda : self.edit_selected_steam( entity ) ) self.delete_btn.clicked.connect( self.delete_selected_item ) self.view_document_btn.clicked.connect( lambda : self.view_steam_document( entity ) ) def steam_data(self, mode): item = None # if self.view.currentIndex().text() == format_name(self.party): # return None, None # One item is selected and number of feature is also 1 if len(self.layer.selectedFeatures()) == 1 and \ len(self.view.selectedIndexes()) == 1: index = self.view.selectedIndexes()[0] item = self.model.itemFromIndex(index) result = item.data() # One item is selected on the map but not on the treeview elif len(self.layer.selectedFeatures()) == 1 and \ len(self.view.selectedIndexes()) == 0: item = self.model.item(0, 0) result = item.data() # multiple features are selected but one treeview item is selected elif len(self.layer.selectedFeatures()) > 1 and \ len(self.view.selectedIndexes()) == 1: item = self.selected_root result = self.selected_root.data() # multiple features are selected but no treeview item is selected elif len(self.layer.selectedFeatures()) > 1 and \ len(self.view.selectedIndexes()) == 0: result = 'Please, select an item to {}.'.format(mode) else: result = 'Please, select at least one feature to {}.'.format(mode) if result is None: if item is None: item = self.model.item(0, 0) result = item.data() else: result = item.parent().data() return result, item def edit_selected_steam(self, entity): id, item = self.steam_data('edit') feature_edit = True if id is None: return if isinstance(id, str): data_error = QApplication.translate('DetailsTreeView', id) QMessageBox.warning( self.iface.mainWindow(), "Edit Error", data_error ) return if item.text() == 'Social Tenure Relationship': model = self.STR_models[id] feature_edit = False ##TODO add STR wizard edit mode here. elif item.text() == format_name(self.party.short_name): feature_edit = False model = self.party_models[id] editor = EntityEditorDialog( self.party, model, self.iface.mainWindow() ) editor.exec_() else: model = self.feature_models[id] editor = EntityEditorDialog( entity, model, self.iface.mainWindow() ) editor.exec_() #root = self.find_root(entity, id) self.view.expand(item.index()) if feature_edit: self.update_edited_steam(entity, id) else: self.update_edited_steam(self.social_tenure, id) def delete_selected_item(self): str_edit = False id, item = self.steam_data('delete') if isinstance(id, str): data_error = QApplication.translate( 'DetailsTreeView', id ) QMessageBox.warning( self.iface.mainWindow(), 'Delete Error', data_error ) return if item.text() == 'Social Tenure Relationship': str_edit = True db_model = self.STR_models[id] elif item.text() == format_name(self.spatial_unit.short_name) and \ id not in self.feature_STR_model.keys(): db_model = self.feature_models[id] # if spatial unit is linked to STR, don't allow delete elif item.text() == format_name(self.spatial_unit.short_name) and \ id in self.feature_STR_model.keys(): delete_warning = QApplication.translate( 'DetailsTreeView', 'You have to first delete the social tenure \n' 'relationship to delete the {} record.'.format( item.text() ) ) QMessageBox.warning( self.iface.mainWindow(), 'Delete Error', delete_warning ) return # If it is party node, STR exists and don't allow delete. elif item.text() == format_name(self.party.short_name): delete_warning = QApplication.translate( 'DetailsTreeView', 'You have to first delete the social tenure \n' 'relationship to delete the {} record.'.format( item.text() ) ) QMessageBox.warning( self.iface.mainWindow(), 'Delete Error', delete_warning ) return else: return delete_warning = QApplication.translate( 'DetailsTreeView', 'Are you sure you want to delete ' 'the selected record(s)?\n' 'This action cannot be undone.' ) delete_question = QMessageBox.warning( self.iface.mainWindow(), "Delete Warning", delete_warning, QMessageBox.Yes | QMessageBox.No ) if delete_question == QMessageBox.Yes: db_model.delete() if str_edit: del self.STR_models[id] else: self.removed_feature = id del self.feature_models[id] self.updated_removed_steam(str_edit, item) else: return def update_edited_steam(self, entity, feature_id): # remove rows before adding the updated ones. self.layer.setSelectedFeatures( self.feature_models.keys() ) root = self.find_root(entity, feature_id) if root is None: return self.view.selectionModel().select( root.index(), self.view.selectionModel().Select ) self.expand_node(root) self.multi_select_highlight(root.index()) def find_root(self, entity, feature_id): all_roots = self.model.findItems( format_name(entity.short_name) ) root = None for item in all_roots: if item.data() == feature_id: root = item break return root def updated_removed_steam(self, STR_edit, item): if not STR_edit: if len(self.feature_models) > 1: self.refresh_layers() feature_ids = self.feature_models.keys() self.layer.setSelectedFeatures( feature_ids ) else: item.removeRows(0, 2) item.setText('No STR Definded') no_str_icon = QIcon( ':/plugins/stdm/images/icons/remove.png' ) item.setIcon(no_str_icon) def view_steam_document(self, entity): # Slot raised to show the document viewer for the selected entity id, item = self.steam_data('edit') if id is None: return if isinstance(id, str): data_error = QApplication.translate('DetailsTreeView', id) QMessageBox.warning( self.iface.mainWindow(), "Edit Error", data_error ) return if item.text() == 'Social Tenure Relationship': db_model = self.STR_models[id] else: db_model = self.feature_models[id] if not db_model is None: docs = db_model.documents # Notify there are no documents for the selected doc if len(docs) == 0: msg = QApplication.translate( 'EntityBrowser', 'There are no supporting documents ' 'for the selected record.' ) QMessageBox.warning( self, self.doc_viewer_title, msg ) else: self.doc_viewer.load(docs)
pr.addAttributes([ QgsField("name", QVariant.String), QgsField("age", QVariant.Int), QgsField("size", QVariant.Double) ]) vl.updateFields() # tell the vector layer to fetch changes from the provider infos = [[10, 10, "John", 24, 1.73], [40, -60, "Paul", 29, 1.86], [60, 5, "George", 34, 1.69], [0, 45, "Ringo", 73, 1.75]] # add features for i in infos: fet = QgsFeature() fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(i[0], i[1]))) fet.setAttributes(i[2:5]) pr.addFeatures([fet]) # update layer's extent when new features have been added # because change of extent in provider is not propagated to the layer vl.updateExtents() QgsMapLayerRegistry.instance().addMapLayer(vl) highlight = QgsHighlight(iface.mapCanvas(), QgsGeometry.fromPoint(QgsPoint(0, 47)), vl) highlight.setBuffer(1.5) highlight.setColor(QColor('black')) highlight.setFillColor(QColor('blue')) highlight.setWidth(0.5) iface.mapCanvas().refresh()
class LinkerDock(QDockWidget, Ui_linker, SettingDialog): def __init__(self, iface): # QGIS self.iface = iface self.settings = MySettings() self.linkRubber = QgsRubberBand(self.iface.mapCanvas()) self.featureHighlight = None # Relation management self.relationManager = QgsProject.instance().relationManager() self.relationManager.changed.connect(self.loadRelations) self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationWidgetWrapper = None self.editorContext = QgsAttributeEditorContext() self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools()) # GUI QDockWidget.__init__(self) self.setupUi(self) SettingDialog.__init__(self, MySettings(), False, True) self.drawButton.setChecked(self.settings.value("drawEnabled")) self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas()) self.mapTool.setButton(self.identifyReferencingFeatureButton) # Connect signal/slot self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged) self.mapTool.featureIdentified.connect(self.setReferencingFeature) # load relations at start self.loadRelations() def showEvent(self, QShowEvent): self.drawLink() def closeEvent(self, e): self.iface.mapCanvas().unsetMapTool(self.mapTool) self.linkRubber.reset() self.deleteHighlight() self.deleteWrapper() self.disconnectLayer() def disconnectLayer(self): if self.relation.isValid(): self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside) def runForFeature(self, relationId, layer, feature): index = self.relationComboBox.findData(relationId) self.relationComboBox.setCurrentIndex(index) self.setReferencingFeature(feature) self.show() if not layer.isEditable(): self.iface.messageBar().pushMessage("Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4) else: self.relationReferenceWidget.mapIdentification() @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked") def activateMapTool(self): self.iface.mapCanvas().setMapTool(self.mapTool) def deactivateMapTool(self): self.iface.mapCanvas().unsetMapTool(self.mapTool) def loadRelations(self): self.deleteWrapper() self.disconnectLayer() self.relation = QgsRelation() self.referencingFeature = QgsFeature() self.relationComboBox.currentIndexChanged.disconnect(self.currentRelationChanged) self.relationComboBox.clear() for relation in self.relationManager.referencedRelations(): if relation.referencingLayer().hasGeometryType(): self.relationComboBox.addItem(relation.name(), relation.id()) self.relationComboBox.setCurrentIndex(-1) self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged) self.currentRelationChanged(-1) def currentRelationChanged(self, index): # disconnect previous relation if self.relation.isValid(): try: self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside) except TypeError: pass self.referencingFeatureLayout.setEnabled(index >= 0) relationId = self.relationComboBox.itemData(index) self.relation = self.relationManager.relation(relationId) self.mapTool.setLayer(self.relation.referencingLayer()) self.setReferencingFeature() # connect if self.relation.isValid(): self.relation.referencingLayer().editingStarted.connect(self.relationEditableChanged) self.relation.referencingLayer().editingStopped.connect(self.relationEditableChanged) self.relation.referencingLayer().attributeValueChanged.connect(self.layerValueChangedOutside) def setReferencingFeature(self, feature=QgsFeature()): self.deactivateMapTool() self.referencingFeature = QgsFeature(feature) self.deleteWrapper() # disable relation reference widget if no referencing feature self.referencedFeatureLayout.setEnabled(feature.isValid()) # set line edit if not self.relation.isValid() or not feature.isValid(): self.referencingFeatureLineEdit.clear() return self.referencingFeatureLineEdit.setText("%s" % feature.id()) fieldIdx = self.referencingFieldIndex() widgetConfig = self.relation.referencingLayer().editorWidgetV2Config(fieldIdx) self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create("RelationReference", self.relation.referencingLayer(), fieldIdx, widgetConfig, self.relationReferenceWidget, self, self.editorContext) self.relationWidgetWrapper.setEnabled(self.relation.referencingLayer().isEditable()) self.relationWidgetWrapper.setValue(feature[fieldIdx]) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) # override field definition to allow map identification self.relationReferenceWidget.setAllowMapIdentification(True) self.relationReferenceWidget.setEmbedForm(False) # update drawn link self.highlightReferencingFeature() self.drawLink() def deleteWrapper(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged) self.relationWidgetWrapper.setValue(None) del self.relationWidgetWrapper self.relationWidgetWrapper = None def foreignKeyChanged(self, newKey): if not self.relation.isValid() or not self.relation.referencingLayer().isEditable() or not self.referencingFeature.isValid(): self.drawLink() return if not self.relation.referencingLayer().editBuffer().changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey): self.iface.messageBar().pushMessage("Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL) self.drawLink() def relationEditableChanged(self): if self.relationWidgetWrapper is not None: self.relationWidgetWrapper.setEnabled(self.relation.isValid() and self.relation.referencingLayer().isEditable()) def layerValueChangedOutside(self, fid, fieldIdx, value): if not self.relation.isValid() or not self.referencingFeature.isValid() or self.relationWidgetWrapper is None: return # not the correct feature if fid != self.referencingFeature.id(): return # not the correct field if fieldIdx != self.referencingFieldIndex(): return # widget already has this value if value == self.relationWidgetWrapper.value(): return self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged) self.relationWidgetWrapper.setValue(value) self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged) def referencingFieldIndex(self): if not self.relation.isValid(): return -1 fieldName = self.relation.fieldPairs().keys()[0] fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName) return fieldIdx @pyqtSlot(bool, name="on_drawButton_toggled") def drawLink(self): self.settings.setValue("drawEnabled", self.drawButton.isChecked()) self.linkRubber.reset() if not self.drawButton.isChecked() or not self.referencingFeature.isValid() or not self.relation.isValid(): return referencedFeature = self.relationReferenceWidget.referencedFeature() if not referencedFeature.isValid(): return p1 = self.centroid(self.relation.referencedLayer(), referencedFeature) p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature) geom = arc(p1, p2) self.linkRubber.setToGeometry(geom, None) self.linkRubber.setWidth(self.settings.value("rubberWidth")) self.linkRubber.setColor(self.settings.value("rubberColor")) self.linkRubber.setLineStyle(Qt.DashLine) def centroid(self, layer, feature): geom = feature.geometry() if geom.type() == QGis.Line: geom = geom.interpolate(geom.length()/2) else: geom = geom.centroid() return self.iface.mapCanvas().mapSettings().layerToMapCoordinates(layer, geom.asPoint()) @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked") def highlightReferencingFeature(self): self.deleteHighlight() if not self.relation.isValid() or not self.referencingFeature.isValid(): return self.featureHighlight = QgsHighlight(self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer()) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha())) bbuffer = float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM)) minWidth = float(settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.featureHighlight.setColor(color) color.setAlpha(alpha) self.featureHighlight.setFillColor(color) self.featureHighlight.setBuffer(bbuffer) self.featureHighlight.setMinWidth(minWidth) self.featureHighlight.show() timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(self.deleteHighlight) timer.start(3000) def deleteHighlight(self): if self.featureHighlight: del self.featureHighlight self.featureHighlight = None
class TidalPredictionWidget(QtWidgets.QDockWidget, FORM_CLASS): TEMPORAL_HACK_SECS = 1 AUTOLOAD_TIMER_MSECS = 300 def __init__(self, parent, canvas): """Constructor.""" super(TidalPredictionWidget, self).__init__(parent) self.canvas = canvas self.temporal = canvas.temporalController() self.setupUi(self) self.tableWidget.setColumnCount(3) self.tableWidget.setSortingEnabled(False) self.tableWidget.setHorizontalHeaderLabels( [tr('Time'), tr('Direction'), tr('Speed')]) self.dateEdit.dateChanged.connect(self.updateDate) self.dateEdit.dateChanged.connect(self.loadStationPredictions) self.timeEdit.timeChanged.connect(self.updateTime) self.nextDay.clicked.connect(lambda: self.adjustDay(1)) self.prevDay.clicked.connect(lambda: self.adjustDay(-1)) self.nextStep.clicked.connect(lambda: self.adjustStep(1)) self.prevStep.clicked.connect(lambda: self.adjustStep(-1)) self.annotationButton.clicked.connect(self.annotatePredictions) self.predictionManager = None self.stationFeature = None self.stationZone = None self.stationHighlight = None self.active = False self.predictionCanvas = None self.includeCurrentsInTable = False self.autoLoadTimer = QTimer() self.autoLoadTimer.setSingleShot(True) self.progressBar.hide() def activate(self): if currentStationsLayer() is None or currentPredictionsLayer() is None: QMessageBox.critical( None, None, tr('You must add current station layers before this tool can be used.' )) return self.show() if not self.active: self.active = True self.currentStationsLayer = currentStationsLayer() self.currentPredictionsLayer = currentPredictionsLayer() self.predictionManager = PredictionManager( self.currentStationsLayer, self.currentPredictionsLayer) self.predictionManager.progressChanged.connect( self.predictionProgress) self.setTemporalRange() self.loadMapExtentPredictions() self.autoLoadTimer.timeout.connect(self.loadMapExtentPredictions) self.canvas.extentsChanged.connect(self.triggerAutoLoad) QgsProject.instance().layerWillBeRemoved.connect(self.removalCheck) def deactivate(self): self.hide() if self.active: self.canvas.extentsChanged.disconnect(self.triggerAutoLoad) self.autoLoadTimer.timeout.disconnect( self.loadMapExtentPredictions) self.autoLoadTimer.stop() QgsProject.instance().layerWillBeRemoved.disconnect( self.removalCheck) self.tableWidget.clearContents() if self.predictionCanvas is not None: self.predictionCanvas.hide() if self.stationHighlight is not None: self.stationHighlight.hide() self.predictionManager.progressChanged.disconnect( self.predictionProgress) self.predictionManager = None self.stationFeature = None self.active = False def predictionProgress(self, progress): if progress == 100: self.progressBar.hide() else: self.progressBar.show() self.progressBar.setValue(progress) def maxAutoLoadCount(self): return 100 # TODO: have a widget for this def removalCheck(self, layerId): if layerId == self.currentStationsLayer.id( ) or layerId == self.currentPredictionsLayer.id(): self.deactivate() def triggerAutoLoad(self): self.autoLoadTimer.start(self.AUTOLOAD_TIMER_MSECS) def loadMapExtentPredictions(self): self.autoLoadTimer.stop() """ ensure all stations in visible extent of the map are loaded """ if self.active and self.predictionManager is not None: xform = QgsCoordinateTransform( self.canvas.mapSettings().destinationCrs(), epsg4326, QgsProject.instance()) rect = xform.transform(self.canvas.extent()) mapFeatures = self.predictionManager.getExtentStations(rect) print('autoloading ', len(mapFeatures), ' in ', rect) if len(mapFeatures) <= self.maxAutoLoadCount(): for f in mapFeatures: self.predictionManager.getDataPromise( f, self.dateEdit.date()).start() if self.stationZone is None and len(mapFeatures) > 0: self.stationZone = stationTimeZone(mapFeatures[0]) def loadStationPredictions(self): """ load predictions for the selected station """ if self.stationFeature is None: return self.stationData = self.predictionManager.getDataPromise( self.stationFeature, self.dateEdit.date()) self.tableWidget.clearContents() self.stationData.resolved(self.predictionsResolved) self.stationData.start() def setTemporalRange(self): """ Set up the temporal range of either based on the current time, or on the temporal extents in the map canvas if those are defined. """ if self.temporal.navigationMode( ) == QgsTemporalNavigationObject.NavigationMode.NavigationOff: startTime = QDateTime.currentDateTime().toUTC() else: startTime = self.temporal.temporalExtents().begin().addSecs( self.TEMPORAL_HACK_SECS) self.setDateTime(startTime) def setDateTime(self, datetime): """ Set our date and time choosers appropriately based on the given UTC time as interpreted for the current station if there is one, else base on local time. """ if self.stationFeature: localtime = datetime.toTimeZone( stationTimeZone(self.stationFeature)) else: localtime = datetime.toLocalTime() self.dateEdit.setDate(localtime.date()) # round off the time to the nearest step displayTime = localtime.time() displayTime.setHMS( displayTime.hour(), PredictionManager.STEP_MINUTES * (displayTime.minute() // PredictionManager.STEP_MINUTES), 0) self.timeEdit.setTime(displayTime) self.updateTime() self.loadStationPredictions() def setCurrentStation(self, feature): """ set the panel's current prediction station to the one described by the given feature """ self.stationFeature = feature self.stationZone = stationTimeZone(feature) self.stationLabel.setText(feature['name']) self.updateTime() self.updateStationLink() self.loadStationPredictions() if self.stationHighlight is not None: self.stationHighlight.hide() self.stationHighlight = QgsHighlight(self.canvas, self.stationFeature, self.currentStationsLayer) self.stationHighlight.setColor(QColor(Qt.red)) self.stationHighlight.setFillColor(QColor(Qt.red)) self.stationHighlight.show() def adjustDay(self, delta): self.dateEdit.setDate(self.dateEdit.date().addDays(delta)) def adjustStep(self, delta): step = 60 * PredictionManager.STEP_MINUTES * delta curTime = self.timeEdit.time() if step < 0 and curTime.hour() == 0 and curTime.minute() == 0: self.adjustDay(-1) curTime = curTime.addSecs(step) self.timeEdit.setTime(curTime) if step > 0 and curTime.hour() == 0 and curTime.minute() == 0: self.adjustDay(1) def updateDate(self): self.loadMapExtentPredictions() self.updateStationLink() self.updateTime() def updateTime(self): self.temporal.setNavigationMode( QgsTemporalNavigationObject.NavigationMode.FixedRange) if self.stationZone is not None: self.datetime = QDateTime(self.dateEdit.date(), self.timeEdit.time(), self.stationZone).toUTC() else: self.datetime = QDateTime(self.dateEdit.date(), self.timeEdit.time()).toUTC() # Note: we hack around a memory provider range bug here by offsetting the window by 1 minute self.temporal.setTemporalExtents( QgsDateTimeRange( self.datetime.addSecs(-self.TEMPORAL_HACK_SECS), self.datetime.addSecs((60 * PredictionManager.STEP_MINUTES) - self.TEMPORAL_HACK_SECS), True, False)) self.updatePlotXLine() def updateStationLink(self): if self.stationFeature is not None: linkUrl = 'https://tidesandcurrents.noaa.gov/noaacurrents/Predictions?id={}&d={}&r=1&tz=LST%2FLDT' linkUrl = linkUrl.format( self.stationFeature['station'], self.dateEdit.date().toString('yyyy-MM-dd')) self.linkLabel.setText('<a href="{}">{} Station Page</a>'.format( linkUrl, self.stationFeature['id'])) else: self.linkLabel.setText('') def handlePlotClick(self, event): minutes = PredictionManager.STEP_MINUTES * ( event.xdata * 60 // PredictionManager.STEP_MINUTES) self.timeEdit.setTime(QTime(minutes // 60, minutes % 60)) def updatePlotXLine(self): if self.predictionCanvas is not None: x = QTime(0, 0).secsTo(self.timeEdit.time()) / 3600.0 self.plotXLine.set_xdata([x, x]) self.plotAxes.figure.canvas.draw() def predictionsResolved(self): # Check to see if the resolved signal is for data we currently care about. # if not, then just bail if self.stationData is None or self.stationData.state != PredictionPromise.ResolvedState: return """ when we have predictions for the current station, show them in the plot and table widget. """ if self.predictionCanvas is not None: self.predictionCanvas.mpl_disconnect(self.plotCallbackId) self.plotLayout.removeWidget(self.predictionCanvas) self.predictionCanvas.hide() self.predictionCanvas = FigureCanvas(Figure(figsize=(5, 3))) self.plotLayout.addWidget(self.predictionCanvas) self.plotAxes = self.predictionCanvas.figure.subplots() # zero time in this plot = 00:00 local time on the date of interest t0 = QDateTime(self.dateEdit.date(), QTime(0, 0), stationTimeZone(self.stationFeature)).toUTC() t = [] val = [] for f in self.stationData.predictions: if f['type'] == 'current': utcTime = f['time'] utcTime.setTimeSpec(Qt.TimeSpec.UTC) t.append(t0.secsTo(utcTime) / 3600) val.append(f['value']) self.plotAxes.set_xlim(left=0, right=24) self.plotAxes.set_xticks([0, 3, 6, 9, 12, 15, 18, 21, 24]) self.plotAxes.grid(linewidth=0.5) y0line = self.plotAxes.axhline(y=0) y0line.set_linestyle(':') y0line.set_linewidth(1) self.plotXLine = self.plotAxes.axvline(x=0) self.plotXLine.set_linestyle(':') self.plotXLine.set_linewidth(1) self.updatePlotXLine() self.plotAxes.plot(t, val) self.plotCallbackId = self.predictionCanvas.mpl_connect( 'button_release_event', self.handlePlotClick) QgsProject.instance()._ax = self.plotAxes self.tableWidget.setRowCount(len(self.stationData.predictions)) i = 0 for p in self.stationData.predictions: dt = p['time'] dt.setTimeSpec(Qt.TimeSpec.UTC) if self.includeCurrentsInTable and p[ 'type'] == 'current' and p['dir'] != NULL: self.tableWidget.setItem( i, 0, QTableWidgetItem( dt.toTimeZone(self.stationZone).toString('h:mm AP'))) self.tableWidget.setItem( i, 1, QTableWidgetItem(str(round(p['dir'])) + 'º')) self.tableWidget.setItem( i, 2, QTableWidgetItem("{:.2f}".format(p['magnitude']))) i += 1 elif p['type'] != 'current': self.tableWidget.setItem( i, 0, QTableWidgetItem( dt.toTimeZone(self.stationZone).toString('h:mm AP'))) self.tableWidget.setItem(i, 1, QTableWidgetItem(p['type'])) self.tableWidget.setItem( i, 2, QTableWidgetItem("{:.2f}".format(p['value']))) self.tableWidget.setRowHeight(i, 20) i += 1 self.tableWidget.setRowCount(i) def annotatePredictions(self): if self.stationFeature is None: return a = QgsTextAnnotation() a.setMapLayer(self.predictionManager.stationsLayer) document = a.document() columnWidth = [80, 100, 60] columnAlign = ['left', 'left', 'right'] html = '<font size="+2"><b>' html += self.stationFeature['name'] + '<br>' + self.dateEdit.date( ).toString() + '<br>' html += '</b></font>' html += '<font size="+1"><table cellpadding="0" cellspacing="0">' html += '<tr>' for j in range(0, self.tableWidget.columnCount()): html += '<td width="{}"><b>{}</b></td>'.format( columnWidth[j], self.tableWidget.horizontalHeaderItem(j).text()) html += '</tr>' for i in range(0, self.tableWidget.rowCount()): html += '<tr bgcolor="{}">'.format('#FFFFFF' if i % 2 else '#EEEEEE') for j in range(0, self.tableWidget.columnCount()): html += '<td align="{}" width="{}">{}</td>'.format( columnAlign[j], columnWidth[j], self.tableWidget.item(i, j).text()) html += '</tr>' html += '</table></font>' document.setHtml(html) # TODO: this size and offset are wack. Can we dynamically calculate from the content somehow? a.setFrameSize(QSizeF(270, 300)) a.setFrameOffsetFromReferencePoint(QPointF(-300, -200)) a.setMapPosition(self.stationFeature.geometry().asPoint()) a.setMapPositionCrs( QgsCoordinateReferenceSystem( self.predictionManager.stationsLayer.crs())) # disable its symbol for symbol in a.markerSymbol().symbolLayers(): symbol.setEnabled(False) QgsProject.instance().annotationManager().addAnnotation(a)