class FeatureGridCreatorDialogTest(unittest.TestCase): """Test dialog works.""" def setUp(self): """Runs before each test.""" self.dialog = FeatureGridCreatorDialog(None) def tearDown(self): """Runs after each test.""" self.dialog = None def test_dialog_ok(self): """Test we can click OK.""" button = self.dialog.button_box.button(QDialogButtonBox.Ok) button.click() result = self.dialog.result() self.assertEqual(result, QDialog.Accepted) def test_dialog_cancel(self): """Test we can click cancel.""" button = self.dialog.button_box.button(QDialogButtonBox.Cancel) button.click() result = self.dialog.result() self.assertEqual(result, QDialog.Rejected)
def setUp(self): """Runs before each test.""" self.dialog = FeatureGridCreatorDialog(None)
def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n', '{}.qm'.format(locale)) #'FeatureGridCreator_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # # CONSTANTS # self.SETTINGS_SECTION = 'gridcreator' self.MSG_BOX_TITLE = 'Grid creator' self.MSG_TRENCHES = self.tr('trenches') self.MSG_HOLES = self.tr('holes') self.MSG_ABOUT = self.tr('Written by Richard Duivenvoorde (Zuidt)\nEmail - [email protected]\n' + 'Funded by - SOB Research - http://www.sobresearch.nl\n' + 'Source: https://github.com/rduivenvoorde/featuregridcreator') self.MSG_NO_ACTIVE_LAYER = self.tr('No active layer found\n' + 'Please make one (multi)-polygon or (multi)-line layer active by choosing a layer in the legend') self.MSG_NO_VECTOR_LAYER = self.tr('Please make one vector layer active by choosing a vector layer in the legend') self.MSG_WRONG_GEOM_TYPE = self.tr('Please make one (multi)-polygon or (multi)-line layer layer active by choosing a layer in the legend') self.MSG_NO_VALID_LAYER = self.tr('No VALID layer found\n' + \ 'Please make one (multi)-polygon or (multi)-line layer active by choosing a layer in the legend') self.MSG_LAYER_NOT_EDITABLE = self.tr('Layer is not editable\n' + \ 'Please make it editable, then enable this tool again and hover over the selected features.') self.MSG_NO_SELECTED_FEATURES = self.tr('Layer has no selected features\n' + \ 'Please select a set of features first, then enable this tool again and hover over the features.') self.MSG_NO_METER_LAYER = self.tr('Layers crs is not in Unit "meters"\n' + \ 'Please use data which has crs in meters, not in e.g. Degrees (not LatLon).') self.GRID_SQUARE = 1 self.GRID_DIAMOND = 2 self.POINT_FEATURES = 1 self.TRENCH_FEATURES = 2 self.RESULT_FEATURE_POINT = 0 self.RESULT_FEATURE_TRENCH_STRAIGHT = 1 self.RESULT_FEATURE_TRENCH_BENDED_OR_SHORT = 2 # # MAIN DIALOG # # Create the main dialog (after translation) and keep reference self.dlg = FeatureGridCreatorDialog() self.dlg.setModal(True) # init dx and dy values in dialog self.dlg.spinBox_dx.setValue(float(self.dx())) self.dlg.spinBox_dy.setValue(float(self.dy())) self.dlg.spinBox_dx.valueChanged.connect(self.dx_change_slot) self.dlg.spinBox_dy.valueChanged.connect(self.dy_change_slot) self.dlg.cbx_inside_polygons.toggled.connect(self.inside_polygons_change_slot) # set current value from settings self.dlg.cbx_inside_polygons.setChecked(self.inside_polygons()) # init dx and dy values in dialog self.dlg.spinBox_trench_width.setValue(int(self.trench_width())) self.dlg.spinBox_trench_length.setValue(int(self.trench_length())) self.dlg.spinBox_trench_width.valueChanged.connect(self.trench_width_change_slot) self.dlg.spinBox_trench_length.valueChanged.connect(self.trench_length_change_slot) self.dlg.buttonBox.button(QDialogButtonBox.Apply).clicked.connect(self.create_features) self.dlg.buttonBox.helpRequested.connect(self.help) self.dlg.buttonBox.rejected.connect(self.hide_create_features_dialog) self.grid_shape_group = QButtonGroup() # NOTE: first add to group, THEN assign an id to it self.grid_shape_group.addButton(self.dlg.radio_square) self.grid_shape_group.setId(self.dlg.radio_square, self.GRID_SQUARE) self.grid_shape_group.addButton(self.dlg.radio_diamond) self.grid_shape_group.setId(self.dlg.radio_diamond, self.GRID_DIAMOND) self.grid_shape_group.buttonReleased.connect(self.grid_shape_change_slot) # set current value from settings self.grid_shape_group.button(self.grid_shape()).setChecked(True) # create a 'button group' for buttons with feature types self.feature_type_group = QButtonGroup() self.feature_type_group.addButton(self.dlg.radio_points) self.feature_type_group.setId(self.dlg.radio_points, self.POINT_FEATURES) self.feature_type_group.addButton(self.dlg.radio_trenches) self.feature_type_group.setId(self.dlg.radio_trenches, self.TRENCH_FEATURES) self.feature_type_group.buttonReleased.connect(self.feature_type_change_slot) self.feature_type_group.button(self.feature_type()).setChecked(True) self.feature_type_change_slot() # force a signal because above 'setChecked' does not fire a change signal? # # LABEL DIALOG # self.lbl_dlg = FeatureGridCreatorLabelerDialog() self.lbl_dlg.le_prefix.setText(self.lbl_prefix()) self.lbl_dlg.spinbox_number.setValue(self.lbl_number()) self.lbl_dlg.le_postfix.setText(self.lbl_postfix()) self.lbl_dlg.le_prefix.textChanged.connect(self.lbl_prefix_change_slot) self.lbl_dlg.spinbox_number.valueChanged.connect(self.lbl_number_change_slot) self.lbl_dlg.le_postfix.textChanged.connect(self.lbl_postfix_change_slot) self.lbl_dlg.buttonBox.helpRequested.connect(self.help) self.label_example() # init the example string # Declare instance attributes self.actions = [] self.tool = None self.layer = None self.menu = self.tr(u'&Feature Grid Creator') self.toolbar = self.iface.addToolBar(u'FeatureGridCreator') self.toolbar.setObjectName(u'FeatureGridCreator')
class FeatureGridCreator: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n', '{}.qm'.format(locale)) #'FeatureGridCreator_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # # CONSTANTS # self.SETTINGS_SECTION = 'gridcreator' self.MSG_BOX_TITLE = 'Grid creator' self.MSG_TRENCHES = self.tr('trenches') self.MSG_HOLES = self.tr('holes') self.MSG_ABOUT = self.tr('Written by Richard Duivenvoorde (Zuidt)\nEmail - [email protected]\n' + 'Funded by - SOB Research - http://www.sobresearch.nl\n' + 'Source: https://github.com/rduivenvoorde/featuregridcreator') self.MSG_NO_ACTIVE_LAYER = self.tr('No active layer found\n' + 'Please make one (multi)-polygon or (multi)-line layer active by choosing a layer in the legend') self.MSG_NO_VECTOR_LAYER = self.tr('Please make one vector layer active by choosing a vector layer in the legend') self.MSG_WRONG_GEOM_TYPE = self.tr('Please make one (multi)-polygon or (multi)-line layer layer active by choosing a layer in the legend') self.MSG_NO_VALID_LAYER = self.tr('No VALID layer found\n' + \ 'Please make one (multi)-polygon or (multi)-line layer active by choosing a layer in the legend') self.MSG_LAYER_NOT_EDITABLE = self.tr('Layer is not editable\n' + \ 'Please make it editable, then enable this tool again and hover over the selected features.') self.MSG_NO_SELECTED_FEATURES = self.tr('Layer has no selected features\n' + \ 'Please select a set of features first, then enable this tool again and hover over the features.') self.MSG_NO_METER_LAYER = self.tr('Layers crs is not in Unit "meters"\n' + \ 'Please use data which has crs in meters, not in e.g. Degrees (not LatLon).') self.GRID_SQUARE = 1 self.GRID_DIAMOND = 2 self.POINT_FEATURES = 1 self.TRENCH_FEATURES = 2 self.RESULT_FEATURE_POINT = 0 self.RESULT_FEATURE_TRENCH_STRAIGHT = 1 self.RESULT_FEATURE_TRENCH_BENDED_OR_SHORT = 2 # # MAIN DIALOG # # Create the main dialog (after translation) and keep reference self.dlg = FeatureGridCreatorDialog() self.dlg.setModal(True) # init dx and dy values in dialog self.dlg.spinBox_dx.setValue(float(self.dx())) self.dlg.spinBox_dy.setValue(float(self.dy())) self.dlg.spinBox_dx.valueChanged.connect(self.dx_change_slot) self.dlg.spinBox_dy.valueChanged.connect(self.dy_change_slot) self.dlg.cbx_inside_polygons.toggled.connect(self.inside_polygons_change_slot) # set current value from settings self.dlg.cbx_inside_polygons.setChecked(self.inside_polygons()) # init dx and dy values in dialog self.dlg.spinBox_trench_width.setValue(int(self.trench_width())) self.dlg.spinBox_trench_length.setValue(int(self.trench_length())) self.dlg.spinBox_trench_width.valueChanged.connect(self.trench_width_change_slot) self.dlg.spinBox_trench_length.valueChanged.connect(self.trench_length_change_slot) self.dlg.buttonBox.button(QDialogButtonBox.Apply).clicked.connect(self.create_features) self.dlg.buttonBox.helpRequested.connect(self.help) self.dlg.buttonBox.rejected.connect(self.hide_create_features_dialog) self.grid_shape_group = QButtonGroup() # NOTE: first add to group, THEN assign an id to it self.grid_shape_group.addButton(self.dlg.radio_square) self.grid_shape_group.setId(self.dlg.radio_square, self.GRID_SQUARE) self.grid_shape_group.addButton(self.dlg.radio_diamond) self.grid_shape_group.setId(self.dlg.radio_diamond, self.GRID_DIAMOND) self.grid_shape_group.buttonReleased.connect(self.grid_shape_change_slot) # set current value from settings self.grid_shape_group.button(self.grid_shape()).setChecked(True) # create a 'button group' for buttons with feature types self.feature_type_group = QButtonGroup() self.feature_type_group.addButton(self.dlg.radio_points) self.feature_type_group.setId(self.dlg.radio_points, self.POINT_FEATURES) self.feature_type_group.addButton(self.dlg.radio_trenches) self.feature_type_group.setId(self.dlg.radio_trenches, self.TRENCH_FEATURES) self.feature_type_group.buttonReleased.connect(self.feature_type_change_slot) self.feature_type_group.button(self.feature_type()).setChecked(True) self.feature_type_change_slot() # force a signal because above 'setChecked' does not fire a change signal? # # LABEL DIALOG # self.lbl_dlg = FeatureGridCreatorLabelerDialog() self.lbl_dlg.le_prefix.setText(self.lbl_prefix()) self.lbl_dlg.spinbox_number.setValue(self.lbl_number()) self.lbl_dlg.le_postfix.setText(self.lbl_postfix()) self.lbl_dlg.le_prefix.textChanged.connect(self.lbl_prefix_change_slot) self.lbl_dlg.spinbox_number.valueChanged.connect(self.lbl_number_change_slot) self.lbl_dlg.le_postfix.textChanged.connect(self.lbl_postfix_change_slot) self.lbl_dlg.buttonBox.helpRequested.connect(self.help) self.label_example() # init the example string # Declare instance attributes self.actions = [] self.tool = None self.layer = None self.menu = self.tr(u'&Feature Grid Creator') self.toolbar = self.iface.addToolBar(u'FeatureGridCreator') self.toolbar.setObjectName(u'FeatureGridCreator') def about(self): QMessageBox.information(self.iface.mainWindow(), "Feature Grid Creator About", self.MSG_ABOUT) def help(self): docs = os.path.join(os.path.dirname(__file__), "help/html/en", "index.html") QDesktopServices.openUrl(QUrl("file:" + docs)) def get_settings_value(self, key, default=''): if QSettings().contains(self.SETTINGS_SECTION + key): key = self.SETTINGS_SECTION + key val = QSettings().value(key) return val else: return default def set_settings_value(self, key, value): key = self.SETTINGS_SECTION + key QSettings().setValue(key, value) # # MAIN DIALOG setters/getters and slots # # getter/setter for dx (saved in settings) def dx(self, value=None): if value is None: return float(self.get_settings_value('dx', '10')) else: self.set_settings_value('dx', value) # getter/setter for dy (saved in settings) def dy(self, value=None): if value is None: return float(self.get_settings_value('dy', '10')) else: self.set_settings_value('dy', value) def trench_width(self, value=None): if value is None: return float(self.get_settings_value('trench_width', '100')) else: self.set_settings_value('trench_width', value) def trench_length(self, value=None): if value is None: return float(self.get_settings_value('trench_height', '100')) else: self.set_settings_value('trench_height', value) def grid_shape(self, value=None): if value is None: return int(self.get_settings_value('grid_shape', str(self.GRID_SQUARE))) else: self.set_settings_value('grid_shape', value) def inside_polygons(self, value=None): if value is None: return self.get_settings_value('inside_polygons', 'True') in ['true', 'True', True] else: self.set_settings_value('inside_polygons', value) def feature_type(self, value=None): if value is None: return int(self.get_settings_value('feature_type', str(self.POINT_FEATURES))) else: self.set_settings_value('feature_type', value) # # SLOTS # def dx_change_slot(self, dx): self.dx(dx) def dy_change_slot(self, dy): self.dy(dy) def trench_width_change_slot(self, w): self.trench_width(w) def trench_length_change_slot(self, h): self.trench_length(h) def grid_shape_change_slot(self): self.grid_shape(self.grid_shape_group.checkedId()) def inside_polygons_change_slot(self): self.inside_polygons(self.dlg.cbx_inside_polygons.isChecked()) def feature_type_change_slot(self): self.feature_type(self.feature_type_group.checkedId()) self.dlg.spinBox_trench_length.setEnabled(self.dlg.radio_trenches.isChecked()) self.dlg.spinBox_trench_width.setEnabled(self.dlg.radio_trenches.isChecked()) self.dlg.lbl_trench_length.setEnabled(self.dlg.radio_trenches.isChecked()) self.dlg.lbl_trench_width.setEnabled(self.dlg.radio_trenches.isChecked()) # # LABEL DIALOG setters/getters and slots # # getter/setter for label prefix (saved in settings) def lbl_prefix(self, value=None): if value is None: return self.get_settings_value('lbl_prefix', 'prefix') else: self.set_settings_value('lbl_prefix', value) # getter/setter for label number (saved in settings) def lbl_number(self, value=None): if value is None: if self.get_settings_value('lbl_number') == '': return 1 return int(self.get_settings_value('lbl_number', '1')) else: self.set_settings_value('lbl_number', value) # getter/setter for label prefix (saved in settings) def lbl_postfix(self, value=None): if value is None: return self.get_settings_value('lbl_postfix', 'postfix') else: self.set_settings_value('lbl_postfix', value) # # SLOTS # def lbl_prefix_change_slot(self, prefix): self.lbl_prefix(prefix) self.label_example() def lbl_number_change_slot(self, number): self.lbl_number(number) self.label_example() def lbl_postfix_change_slot(self, postfix): self.lbl_postfix(postfix) self.label_example() def label(self, number): lbl = unicode(number) if not self.lbl_prefix() in ['prefix', '']: lbl = self.lbl_prefix() + '_' + lbl if not self.lbl_postfix() in ['postfix', '']: lbl += '_' + self.lbl_postfix() return lbl def label_example(self): self.lbl_dlg.lbl_example.setText(self.label(self.lbl_number()) + ', ' + self.label(self.lbl_number() + 1) + ', ' + self.label(self.lbl_number() + 2) + ' ...') # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('FeatureGridCreator', message) def add_action( self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None ): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToVectorMenu( self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" self.create_action = self.add_action( ':/plugins/FeatureGridCreator/icon.png', text=self.tr(u'Create a grid of trench or holes'), callback=self.show_create_features_dialog, parent=self.iface.mainWindow()) self.create_action.setCheckable(True) self.label_action = self.add_action( ':/plugins/FeatureGridCreator/icon2.png', text=self.tr(u'Label selected features'), callback=self.start_labeling, parent=self.iface.mainWindow()) self.label_action.setCheckable(True) # help self.add_action( ':/plugins/FeatureGridCreator/help.png', text=self.tr(u'Help'), callback=self.help, add_to_toolbar=False, parent=self.iface.mainWindow()) # about self.add_action( ':/plugins/FeatureGridCreator/help.png', text=self.tr(u'About'), callback=self.about, add_to_toolbar=False, parent=self.iface.mainWindow()) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginVectorMenu( self.tr(u'&Feature Grid Creator'), action) self.iface.removeToolBarIcon(action) def show_create_features_dialog(self): """Init and show dialog""" #pydevd.settrace('localhost', port=5678, stdoutToServer=True, stderrToServer=True) if self.init_create_features_dialog(): # show the dialog self.dlg.show() else: return self.create_action.setChecked(True) def hide_create_features_dialog(self): self.create_action.setChecked(False) def init_create_features_dialog(self): """ init dialog based on layer type :param self: :return: true if ok, false if wrong type of layer """ # check if current active layer is a polygon layer: layer = self.iface.activeLayer() layer_problem = False if layer is None: QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_ACTIVE_LAYER), QMessageBox.Ok, QMessageBox.Ok) layer_problem = True elif layer.type() > 0: # 0 = vector, 1 = raster QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_VECTOR_LAYER), QMessageBox.Ok, QMessageBox.Ok) layer_problem = True # don't know if this is possible / needed elif not layer.isValid(): QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_VALID_LAYER), QMessageBox.Ok, QMessageBox.Ok) layer_problem = True if layer_problem: self.create_action.setChecked(False) self.dlg.hide() return False # check if current active VECTOR layer has an OK type geom_type = layer.dataProvider().geometryType() if not(geom_type == QGis.WKBPolygon or geom_type == QGis.WKBMultiPolygon or geom_type == QGis.WKBLineString or geom_type == QGis.WKBMultiLineString or geom_type == QGis.WKBPolygon25D or geom_type == QGis.WKBMultiPolygon25D ): QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_WRONG_GEOM_TYPE), QMessageBox.Ok, QMessageBox.Ok) layer_problem = True # check layer's projection is not a geographical projection force units in meters if layer.crs().mapUnits() != QGis.Meters: QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_METER_LAYER), QMessageBox.Ok, QMessageBox.Ok) layer_problem = True if layer_problem: self.create_action.setChecked(False) self.dlg.hide() return False # disable some dialog parts if geometries in the layer are lines geoms_are_polygons = (geom_type == QGis.WKBPolygon or geom_type == QGis.WKBMultiPolygon or geom_type == QGis.WKBPolygon25D or geom_type == QGis.WKBMultiPolygon25D ) self.dlg.box_dy.setEnabled(geoms_are_polygons) self.dlg.box_grid_shape.setEnabled(geoms_are_polygons) self.dlg.box_inside_polygons.setEnabled(geoms_are_polygons) self.dlg.progress_bar.setValue(0) # now use current active layer as current for the rest of this 'dialog session' self.current_layer = self.iface.mapCanvas().currentLayer() return True def create_features(self): active_layer = self.current_layer # self.current_layer is current when dialog was initalised self.dlg.progress_bar.setValue(0) # for size of progress bar fcount = 0 if len(active_layer.selectedFeatures()) < 1: # NO selection features = active_layer.getFeatures() # iterator fcount = active_layer.featureCount() else: # selected features features = active_layer.selectedFeatures() # features[] fcount = len(features) # give the memory layer the same CRS as the source layer crs = active_layer.crs() if self.feature_type() == self.TRENCH_FEATURES: memory_lyr = QgsVectorLayer("Polygon?crs=epsg:" + unicode(crs.postgisSrid()) + "&index=yes", self.MSG_TRENCHES, "memory") else: memory_lyr = QgsVectorLayer("Point?crs=epsg:" + unicode(crs.postgisSrid()) + "&index=yes", self.MSG_HOLES, "memory") QgsMapLayerRegistry.instance().addMapLayer(memory_lyr) provider = memory_lyr.dataProvider() provider.addAttributes([ QgsField("code", QVariant.String), QgsField("ftype", QVariant.Int)]) # http://snorf.net/blog/2014/03/04/symbology-of-vector-layers-in-qgis-python-plugins/ # Categorized symbol renderer for different type of grid features: points, straight trench and bended or stort trench # define a lookup: value -> (color, label) ftype = { '0': ('#00f', self.tr('hole')), } if self.feature_type() == self.TRENCH_FEATURES: ftype = { '1': ('#00f', self.tr('trench straight')), '2': ('#f00', self.tr('trench bend or short')), #'': ('#000', 'Unknown'), } # create a category for each categories = [] for feature_type, (color, label) in ftype.items(): symbol = QgsSymbolV2.defaultSymbol(memory_lyr.geometryType()) symbol.setColor(QColor(color)) category = QgsRendererCategoryV2(feature_type, symbol, label) categories.append(category) # create the renderer and assign it to a layer expression = 'ftype' # field name renderer = QgsCategorizedSymbolRendererV2(expression, categories) memory_lyr.setRendererV2(renderer) fid = 0 start_x = 0 start_y = 0 ddx = 0 # square grid default if self.grid_shape() == self.GRID_DIAMOND: ddx = 0.5 * self.dx() add_this_one = True fts = [] self.dlg.progress_bar.setMaximum(fcount) for f in features: self.dlg.progress_bar.setValue(self.dlg.progress_bar.value() + 1) if f.geometry().wkbType() == QGis.WKBPolygon or f.geometry().wkbType() == QGis.WKBMultiPolygon or f.geometry().wkbType() == QGis.WKBPolygon25D or f.geometry().wkbType() == QGis.WKBMultiPolygon25D : # polygon bbox = f.geometry().boundingBox() if not self.inside_polygons(): # grow the bbox to be sure it is big enough to be able to rotate it if bbox.width() > bbox.height(): bbox.setYMaximum(bbox.center().y() + bbox.width() / 2) bbox.setYMinimum(bbox.center().y() - bbox.width() / 2) elif bbox.height() > bbox.width(): bbox.setXMaximum(bbox.center().x() + bbox.height() / 2) bbox.setXMinimum(bbox.center().x() - bbox.height() / 2) start_x = bbox.xMinimum() + float(self.dx() / 2) start_y = bbox.yMinimum() + float(self.dy() / 2) for row in range(0, int(math.ceil(bbox.height() / self.dy()))): for column in range(0, int(math.ceil(bbox.width() / self.dx()))): fet = QgsFeature() geom_type = self.create_point_or_trench(start_x, start_y) if self.inside_polygons(): add_this_one = f.geometry().contains(geom_type[0]) if add_this_one: fet.setGeometry(geom_type[0]) #fet.setAttributes([ ''+unicode(fid) ]) fet.setAttributes(['', geom_type[1]]) fts.append(fet) fid += 1 start_x += self.dx() start_x = bbox.xMinimum() + float(self.dx() / 2) if row % 2 == 0: start_x += ddx start_y += self.dy() # lines elif f.geometry().wkbType() == QGis.WKBLineString: if self.feature_type() == self.TRENCH_FEATURES: start_x = 0 fts.extend(self.handle_line(start_x, start_y, self.dx(), f.geometry())) elif f.geometry().wkbType() == QGis.WKBMultiLineString: QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate("featuregridcreator", "Sorry, MultiLinestring currently not supported."), QMessageBox.Ok, QMessageBox.Ok) provider.addFeatures(fts) memory_lyr.updateFields() memory_lyr.updateExtents() self.iface.mapCanvas().refresh() # make layer with new features active and editable self.iface.setActiveLayer(memory_lyr) # select all features in current memoryLayer ids = [] for f in memory_lyr.getFeatures(QgsFeatureRequest()): ids.append(f.id()) memory_lyr.setSelectedFeatures(ids) # set to editing memory_lyr.startEditing() self.layer = memory_lyr self.create_action.setChecked(False) def handle_line(self, start, end, interval, line_geom): """Creating Points or Trenches at coordinates along the line """ length = line_geom.length() distance = start if 0 < end <= length: length = end # array with all generated features feats = [] while distance <= length: # Create a new QgsFeature and assign it the new geometry feature = QgsFeature() feature.setAttributes(['']) geom_type = self.create_point_or_trench_on_line(line_geom, distance, interval) if geom_type[0] is not None: feature.setGeometry(geom_type[0]) feature.setAttributes(['', geom_type[1]]) feats.append(feature) # Increase the distance distance += interval return feats def create_point_or_trench(self, x, y): if self.feature_type() == self.TRENCH_FEATURES: # trench width and length in centimeters and div by 2 w = self.trench_width() / 200 l = self.trench_length() / 200 # a polygon with trenches return QgsGeometry.fromRect(QgsRectangle(x - l, y - w, x + l, y + w)), self.RESULT_FEATURE_TRENCH_STRAIGHT # 1 meaning a straight trench else: # a polygon with points return QgsGeometry.fromPoint(QgsPoint(x, y)), self.RESULT_FEATURE_POINT # 0 meaning a point def create_point_or_trench_on_line(self, line_geom, distance, interval): # Get a point on the line at current distance geom = line_geom.interpolate(distance) # interpolate returns a QgsGeometry if self.feature_type() == self.TRENCH_FEATURES: # trench width and length in centimeters w = self.trench_width() / 100 l = self.trench_length() / 100 x1 = geom.asPoint().x() y1 = geom.asPoint().y() # a non rotated trench #return QgsGeometry.fromRect(QgsRectangle(x1-l, y1-w, x1+l, y1+w)) # a trench in the direction of the line geom2 = line_geom.interpolate(distance + l) # interpolate returns a QgsGeometry-point vertices = [geom.asPoint()] # BUT check if there are vertices on this line_geom in between # see if there are vertices on the path here... vertices.append(geom2.asPoint()) line = QgsGeometry.fromPolyline(vertices) # checking if length of the generated line is as requested # if the difference is more then 1 cm (comparing floats....) # we either do NOT add it, or generate rounded caps if (int(self.trench_length()) - int(line.length() * 100)) > 1.0: # buffer(distance, segments, endcapstyle, joinstyle, mitrelimit) # endcap 2 = flat # join 1 = round #trench = line.buffer(w/2, 4, 1, 1, 1) #trench = None # print line_geom.touches(geom2) # true # line.closestSegmentWithContext(point, minDistPoint, afterVertex, 0, 0.00000001) # returns a segmentWithContext like: (0.0, (104642,490373), 2) # being: distance, point, segmentAfter segment_context = line_geom.closestSegmentWithContext(geom.asPoint()) segment_context2 = line_geom.closestSegmentWithContext(geom2.asPoint()) ii = 1 for i in range(segment_context[2], segment_context2[2]): #new_vertex = line_geom.vertexAt(segment_context[2]) new_vertex = line_geom.vertexAt(i) line.insertVertex(new_vertex.x(), new_vertex.y(), ii) ii += 1 trench = line.buffer(w / 2, 0, 2, 1, 1) # trench = line.buffer(w/2, 1, 1, 1, 1) # 'round' endcap return trench, self.RESULT_FEATURE_TRENCH_BENDED_OR_SHORT # 2 meaning this is not a straight trench (a bended one) else: # buffer(distance, segments, endcapstyle, joinstyle, mitrelimit) # endcap 2 = flat # join 1 = round trench = line.buffer(w / 2, 0, 2, 1, 1) return trench, self.RESULT_FEATURE_TRENCH_STRAIGHT # 1 meaning a straigh trench else: # a line with points return geom, self.RESULT_FEATURE_POINT # 0 meaning a point def start_labeling(self): if not self.label_action.isChecked(): # looks contra intuitive, but action is already checked here! self.stop_labeling() return self.layer = self.iface.activeLayer() self.label_action.setChecked(True) bail_out = False if self.layer is None: QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_ACTIVE_LAYER), QMessageBox.Ok, QMessageBox.Ok) bail_out = True elif self.layer.type() > 0: # 0 = vector, 1 = raster QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_VECTOR_LAYER), QMessageBox.Ok, QMessageBox.Ok) bail_out = True # don't know if this is possible / needed elif not self.layer.isValid(): QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_VALID_LAYER), QMessageBox.Ok, QMessageBox.Ok) bail_out = True elif len(self.layer.selectedFeaturesIds()) == 0: QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_NO_SELECTED_FEATURES), QMessageBox.Ok, QMessageBox.Ok) bail_out = True elif not self.layer.isEditable(): QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, QCoreApplication.translate(self.SETTINGS_SECTION, self.MSG_LAYER_NOT_EDITABLE), QMessageBox.Ok, QMessageBox.Ok) bail_out = True if bail_out: self.label_action.setChecked(False) return False #self.lbl_dlg.show() # Run the dialog event loop result = self.lbl_dlg.exec_() # See if OK was pressed if result == 0: self.stop_labeling() return self.tool = LabelTool(self.iface.mapCanvas(), self.lbl_prefix(), self.lbl_number(), self.lbl_postfix()) self.tool.set_layer(self.layer) self.iface.mapCanvas().setMapTool(self.tool) # deactivate this tool when the layer is being deleted! if self.layer: self.layer.layerDeleted.connect(self.stop_labeling) # deactivate this tool when user selects another layer self.iface.currentLayerChanged.connect(self.stop_labeling) def stop_labeling(self): self.label_action.setChecked(False) self.iface.mapCanvas().unsetMapTool(self.tool)