def convertToPolygon(self, geom): if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry and geom.constGet().nCoordinates() < 3: raise QgsProcessingException( self.tr('Cannot convert from Point to Polygon').format(QgsWkbTypes.displayString(geom.wkbType()))) elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: # multipoint with at least 3 points # TODO: mega inefficient - needs rework when geometry iterators land # (but at least it doesn't lose Z/M values) points = [] for g in geom.constGet().coordinateSequence(): for r in g: for p in r: points.append(p) linestring = QgsLineString(points) linestring.close() p = QgsPolygon() p.setExteriorRing(linestring) return [QgsGeometry(p)] elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: if QgsWkbTypes.isMultiType(geom): parts = [] for i in range(geom.constGet().numGeometries()): p = QgsPolygon() linestring = geom.constGet().geometryN(i).clone() linestring.close() p.setExteriorRing(linestring) parts.append(QgsGeometry(p)) return QgsGeometry.collectGeometry(parts) else: # linestring to polygon p = QgsPolygon() linestring = geom.constGet().clone() linestring.close() p.setExteriorRing(linestring) return [QgsGeometry(p)] else: #polygon if QgsWkbTypes.isMultiType(geom): return geom.asGeometryCollection() else: return [geom]
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) if QgsWkbTypes.isMultiType(source.wkbType()): raise QgsProcessingException(self.tr('Input point layer is a MultiPoint layer - first convert to single points before using this algorithm.')) source_field = self.parameterAsString(parameters, self.INPUT_FIELD, context) target_source = self.parameterAsSource(parameters, self.TARGET, context) if target_source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.TARGET)) if QgsWkbTypes.isMultiType(target_source.wkbType()): raise QgsProcessingException(self.tr('Target point layer is a MultiPoint layer - first convert to single points before using this algorithm.')) target_field = self.parameterAsString(parameters, self.TARGET_FIELD, context) same_source_and_target = parameters[self.INPUT] == parameters[self.TARGET] matType = self.parameterAsEnum(parameters, self.MATRIX_TYPE, context) nPoints = self.parameterAsInt(parameters, self.NEAREST_POINTS, context) if nPoints < 1: nPoints = target_source.featureCount() if matType == 0: # Linear distance matrix return self.linearMatrix(parameters, context, source, source_field, target_source, target_field, same_source_and_target, matType, nPoints, feedback) elif matType == 1: # Standard distance matrix return self.regularMatrix(parameters, context, source, source_field, target_source, target_field, nPoints, feedback) elif matType == 2: # Summary distance matrix return self.linearMatrix(parameters, context, source, source_field, target_source, target_field, same_source_and_target, matType, nPoints, feedback)
def convertToLineStrings(self, geom): if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: raise QgsProcessingException( self.tr('Cannot convert from {0} to LineStrings').format(QgsWkbTypes.displayString(geom.wkbType()))) elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: if QgsWkbTypes.isMultiType(geom.wkbType()): return geom.asGeometryCollection() else: #line to line return [geom] else: # polygons to lines # we just use the boundary here - that consists of all rings in the (multi)polygon boundary = QgsGeometry(geom.constGet().boundary()) # boundary will be multipart return boundary.asGeometryCollection()
def convertToMultiLineStrings(self, geom): if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: raise QgsProcessingException( self.tr('Cannot convert from {0} to MultiLineStrings').format(QgsWkbTypes.displayString(geom.wkbType()))) elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: if QgsWkbTypes.isMultiType(geom.wkbType()): return [geom] else: # line to multiLine ml = QgsMultiLineString() ml.addGeometry(geom.constGet().clone()) return [QgsGeometry(ml)] else: # polygons to multilinestring # we just use the boundary here - that consists of all rings in the (multi)polygon return [QgsGeometry(geom.constGet().boundary())]
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('straightdis', QVariant.Double)) new_fields.append(QgsField('sinuosity', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def load_complex_gml(self, xml_uri, is_remote, attributes = {}, geometry_mapping = None, logger = None, swap_xy = False): """ :param xml_uri: the XML URI :param is_remote: True if it has to be fetched by http :param attributes: { 'attr1' : ( '//xpath/expression', QVariant.Int ) } :param geometry_mapping: XPath expression to a gml geometry node :param swap_xy: True if X/Y coordinates must be swapped :returns: the created layer """ try: if is_remote: xml_src = remote_open_from_qgis(xml_uri) else: # Open the file in binary mode, this means returning bytes # instead of a string whose encoding would have to be interpreted # it is up to the XML parser to determine which encoding it is xml_src = open(xml_uri, 'rb') src = ComplexFeatureSource(xml_src, attributes, geometry_mapping, logger) attr_list = [ (k, v[1]) for k, v in attributes.items() ] layers = {} features = {} layer_geom_type = {} for id, fid, qgsgeoms, xml, attrs in src.getFeatures(swap_xy): # layer creation if qgsgeoms == []: if "" not in layers: layer = self._create_layer('none', None, attr_list, src.title, "nogeom") self._add_properties_to_layer(layer, xml_uri, is_remote, attributes, geometry_mapping) layers["nogeom"] = layer else: for (qgsgeom, srid), tag in qgsgeoms: if tag in layers: continue type2d = QgsWkbTypes.flatType(qgsgeom.wkbType()) typemap = {QgsWkbTypes.Point: 'point', QgsWkbTypes.MultiPoint: 'multipoint', QgsWkbTypes.LineString: 'linestring', QgsWkbTypes.MultiLineString: 'multilinestring', QgsWkbTypes.Polygon: 'polygon', QgsWkbTypes.MultiPolygon: 'multipolygon', QgsWkbTypes.CompoundCurve: 'compoundcurve', QgsWkbTypes.CircularString: 'compoundcurve', QgsWkbTypes.MultiCurve: 'multicurve', QgsWkbTypes.CurvePolygon: 'curvepolygon', QgsWkbTypes.MultiSurface: 'multisurface'} if qgsgeom and type2d in typemap: title = "{} ({})".format(src.title, no_prefix(tag)) layer = self._create_layer(typemap[QgsWkbTypes.multiType(type2d)], srid, attr_list, title, no_prefix(tag)) else: raise RuntimeError("Unsupported geometry type {}".format(qgsgeom.wkbType())) self._add_properties_to_layer(layer, xml_uri, is_remote, attributes, geometry_mapping) layers[tag] = layer # collect features f = QgsFeature(layer.dataProvider().fields(), id) f.setAttribute("id", str(id)) f.setAttribute("fid", fid) for k, v in attrs.items(): r = f.setAttribute(k, v) for g, tag in qgsgeoms: if tag not in features: features[tag] = [] fcopy = QgsFeature(f) fcopy.setAttribute("_xml_", ET.tostring(xml).decode('utf8')) if g: qgsgeom, _ = g if QgsWkbTypes.isMultiType(layers[tag].wkbType()) and QgsWkbTypes.isSingleType(qgsgeom.wkbType()): # force multi qgsgeom.convertToMultiType() fcopy.setGeometry(qgsgeom) features[tag].append(fcopy) # write features for tag, f in features.items(): if len(f) > 0: layer = layers[tag] layer.startEditing() layer.addFeatures(f) layer.commitChanges() finally: xml_src.close() # Set the styl for polygons coming from boundedBy for tag_name, layer in layers.items(): if tag_name.endswith("boundedBy"): layer.loadNamedStyle(os.path.join(os.path.dirname(__file__), "..", "gui", "bounded_by_style.qml")) return layers
def make_features_compatible(new_features, input_layer): """Try to make the new features compatible with old features by: - converting single to multi part - dropping additional attributes - adding back M/Z values - drop Z/M - convert multi part to single part :param new_features: new features :type new_features: list of QgsFeatures :param input_layer: input layer :type input_layer: QgsVectorLayer :return: modified features :rtype: list of QgsFeatures """ input_wkb_type = input_layer.wkbType() result_features = [] for new_f in new_features: # Fix attributes if new_f.fields().count() > 0: attributes = [] for field in input_layer.fields(): if new_f.fields().indexFromName(field.name()) >= 0: attributes.append(new_f[field.name()]) else: attributes.append(None) f = QgsFeature(input_layer.fields()) f.setAttributes(attributes) f.setGeometry(new_f.geometry()) new_f = f else: lendiff = len(new_f.attributes()) - len(input_layer.fields()) if lendiff > 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) f.setAttributes(new_f.attributes()[:len(input_layer.fields())]) new_f = f elif lendiff < 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) attributes = new_f.attributes() + [None for i in range(-lendiff)] f.setAttributes(attributes) new_f = f # Check if we need geometry manipulation new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType()) new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry) input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown) # Drop geometry if layer is geometry-less if not input_layer_has_geom and new_f_has_geom: f = QgsFeature(input_layer.fields()) f.setAttributes(new_f.attributes()) new_f = f result_features.append(new_f) continue # skip the rest if input_layer_has_geom and new_f_has_geom and \ new_f.geometry().wkbType() != input_wkb_type: # Fix geometry # Single -> Multi if (QgsWkbTypes.isMultiType(input_wkb_type) and not new_f.geometry().isMultipart()): new_geom = new_f.geometry() new_geom.convertToMultiType() new_f.setGeometry(new_geom) # Drop Z/M if (new_f.geometry().constGet().is3D() and not QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropZValue() new_f.setGeometry(new_geom) if (new_f.geometry().constGet().isMeasure() and not QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropMValue() new_f.setGeometry(new_geom) # Add Z/M back (set it to 0) if (not new_f.geometry().constGet().is3D() and QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addZValue(0.0) new_f.setGeometry(new_geom) if (not new_f.geometry().constGet().isMeasure() and QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addMValue(0.0) new_f.setGeometry(new_geom) # Multi -> Single if (not QgsWkbTypes.isMultiType(input_wkb_type) and new_f.geometry().isMultipart()): g = new_f.geometry() g2 = g.constGet() for i in range(g2.partCount()): # Clone or crash! g4 = QgsGeometry(g2.geometryN(i).clone()) f = QgsVectorLayerUtils.createFeature(input_layer, g4, {i: new_f.attribute(i) for i in range(new_f.fields().count())}) result_features.append(f) else: result_features.append(new_f) else: result_features.append(new_f) return result_features
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('straightdis', QVariant.Double)) new_fields.append(QgsField('sinuosity', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, layerA.wkbType(), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) multiGeoms = 0 # how many multi geometries were encountered for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() if inGeom.isMultipart(): multiGeoms += 1 # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined: # 1) add both new parts as new features # 2) store one part as a new feature and the other one as part of the multi geometry # 2a) which part should be which, seems arbitrary else: attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged QgsMessageLog.logMessage("appending") outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: QgsMessageLog.logMessage("appending else") outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry \ and not QgsWkbTypes.isMultiType(aGeom.wkbType()): passed = len(aGeom.asPolyline()) > 2 if not passed: passed = (len(aGeom.asPolyline()) == 2 and aGeom.asPolyline()[0] != aGeom.asPolyline()[1]) # sometimes splitting results in lines of zero length if passed: outFeat.setGeometry(aGeom) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) if multiGeoms > 0: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Feature geometry error: %s input features ignored due to multi-geometry.') % str(multiGeoms)) del writer
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.PrmInputLayer, context) decimate_by_distance = self.parameterAsBool(parameters, self.PrmDecimateByDistance, context) decimate_by_time = self.parameterAsBool(parameters, self.PrmDecimateByTime, context) preserve_final_pt = self.parameterAsBool(parameters, self.PrmPreserveFinalPoint, context) min_distance = self.parameterAsDouble(parameters, self.PrmMinDistance, context) units = self.parameterAsInt(parameters, self.PrmUnitsOfMeasure, context) min_time = self.parameterAsDouble(parameters, self.PrmMinTime, context) time_units = self.parameterAsInt(parameters, self.PrmTimeUnits, context) is_or_condition = self.parameterAsInt(parameters, self.PrmTwoConditionResponnse, context) fields = source.fields() if self.PrmTimeField not in parameters or parameters[ self.PrmTimeField] is None or parameters[ self.PrmTimeField] == '': time_field = None time_idx = None else: time_field = self.parameterAsString(parameters, self.PrmTimeField, context) time_idx = fields.indexOf(time_field) if decimate_by_time and time_field is None: msg = tr('Please select a DateTime field when decimating by time') feedback.reportError(msg) raise QgsProcessingException(msg) if self.PrmOrderField not in parameters or parameters[ self.PrmOrderField] is None or parameters[ self.PrmOrderField] == '': order_field = None else: order_field = self.parameterAsString(parameters, self.PrmOrderField, context) if self.PrmGroupField not in parameters or parameters[ self.PrmGroupField] is None or parameters[ self.PrmGroupField] == '': group_field = None else: group_field = self.parameterAsString(parameters, self.PrmGroupField, context) wkbtype = source.wkbType() if QgsWkbTypes.isMultiType(wkbtype): msg = tr('Only supports single part Point geometry') feedback.reportError(msg) raise QgsProcessingException(msg) layercrs = source.sourceCrs() (sink, dest_id) = self.parameterAsSink(parameters, self.PrmOutputLayer, context, fields, wkbtype, layercrs) if layercrs != epsg4326: transto4326 = QgsCoordinateTransform(layercrs, epsg4326, QgsProject.instance()) num_pts = source.featureCount() total = 100.0 / num_pts if num_pts else 0 min_time_s = self.convert_time_to_s(min_time, time_units) min_distance = min_distance * conversionToMeters(units) if group_field: grp_indx = fields.indexOf(group_field) groups = source.uniqueValues(grp_indx) request = QgsFeatureRequest() if order_field: request.addOrderBy(order_field) index = 0 for group in groups: filter = '"{}" = \'{}\''.format(group_field, group) request.setFilterExpression(filter) iterator = source.getFeatures(request) last_decimated = False first_feature = True for feature in iterator: index += 1 if feedback.isCanceled(): break pt = feature.geometry().asPoint() if layercrs != epsg4326: # Convert to 4326 pt4326 = transto4326.transform(pt) else: pt4326 = pt if first_feature: first_feature = False sink.addFeature(feature) pt_last = pt4326 if decimate_by_time: last_time = feature[time_idx] else: d_keep = True t_keep = True if decimate_by_distance: gline = geod.InverseLine(pt_last.y(), pt_last.x(), pt4326.y(), pt4326.x()) if gline.s13 < min_distance: d_keep = False if decimate_by_time: cur_time = feature[time_idx] try: diff = abs( cur_time.toMSecsSinceEpoch() - last_time.toMSecsSinceEpoch()) / 1000.0 if diff < min_time_s: t_keep = False except Exception: pass if is_or_condition and (d_keep or t_keep): if decimate_by_distance: pt_last = pt4326 if decimate_by_time: last_time = cur_time last_decimated = False sink.addFeature(feature) elif d_keep and t_keep: if decimate_by_distance: pt_last = pt4326 if decimate_by_time: last_time = cur_time last_decimated = False sink.addFeature(feature) else: last_decimated = True if index % 100: feedback.setProgress(int(index * total)) if preserve_final_pt and last_decimated: # feature contains the last item from the above for loop which is valid in Python sink.addFeature(feature) else: if order_field: request = QgsFeatureRequest() request.addOrderBy(order_field) iterator = source.getFeatures(request) else: iterator = source.getFeatures() for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break pt = feature.geometry().asPoint() if layercrs != epsg4326: # Convert to 4326 pt4326 = transto4326.transform(pt) else: pt4326 = pt if cnt == 0: # This is the first point so it is saved sink.addFeature(feature) pt_last = pt4326 if decimate_by_time: last_time = feature[time_idx] else: d_keep = True t_keep = True if decimate_by_distance: gline = geod.InverseLine(pt_last.y(), pt_last.x(), pt4326.y(), pt4326.x()) if gline.s13 < min_distance: d_keep = False if decimate_by_time: cur_time = feature[time_idx] try: diff = abs(cur_time.toMSecsSinceEpoch() - last_time.toMSecsSinceEpoch()) / 1000.0 if diff < min_time_s: t_keep = False except Exception: pass if is_or_condition and (d_keep or t_keep): if decimate_by_distance: pt_last = pt4326 if decimate_by_time: last_time = cur_time sink.addFeature(feature) elif d_keep and t_keep: if decimate_by_distance: pt_last = pt4326 if decimate_by_time: last_time = cur_time sink.addFeature(feature) elif preserve_final_pt and (cnt == num_pts - 1): sink.addFeature(feature) if cnt % 100: feedback.setProgress(int(cnt * total)) return {self.PrmOutputLayer: dest_id}
def processAlgorithm(self, parameters, context, feedback): #pylint: disable=unused-argument,missing-docstring layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) resolution = self.parameterAsDouble(parameters, self.RESOLUTION, context) lmax = self.parameterAsDouble(parameters, self.LMAX, context) # max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) if QgsWkbTypes.isMultiType(layer.wkbType()): feedback.reportError( self.tr('Multipart geometries are not currently supported'), True) return {} (axis_sink, axis_id) = self.parameterAsSink(parameters, self.FLOW_AXIS, context, layer.fields(), layer.wkbType(), layer.sourceCrs()) fields = QgsFields(layer.fields()) fields.append(QgsField('BENDID', QVariant.Int, len=10)) fields.append(QgsField('NPTS', QVariant.Int, len=4)) fields.append(QgsField('LBEND', QVariant.Double, len=10, prec=2)) fields.append(QgsField('LWAVE', QVariant.Double, len=10, prec=2)) fields.append(QgsField('SINUO', QVariant.Double, len=6, prec=4)) fields.append(QgsField('AMPLI', QVariant.Double, len=10, prec=4)) fields.append(QgsField('OMEG0', QVariant.Double, len=10, prec=8)) fields.append(QgsField('OMEG1', QVariant.Double, len=10, prec=6)) # QgsField('RCURV', QVariant.Double, len=10, prec=3) (segment_sink, segment_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, layer.wkbType(), layer.sourceCrs()) fields = QgsFields() fields.append(QgsField('GID', QVariant.Int, len=10)) fields.append(QgsField('ANGLE', QVariant.Double, len=10, prec=6)) fields.append(QgsField('INTERDIST', QVariant.Double, len=10, prec=6)) (inflection_sink, inflection_id) = self.parameterAsSink(parameters, self.INFLECTION_POINTS, context, fields, QgsWkbTypes.Point, layer.sourceCrs()) fields = QgsFields() fields.append(QgsField('BENDID', QVariant.Int, len=10)) fields.append(QgsField('AMPLI', QVariant.Double, len=10, prec=4)) (stem_sink, stem_id) = self.parameterAsSink(parameters, self.STEMS, context, fields, QgsWkbTypes.LineString, layer.sourceCrs()) def write_axis_segment(fid, p0, p1, feature): new_feature = QgsFeature() new_feature.setGeometry(QgsGeometry.fromPolylineXY([p0, p1])) new_feature.setAttributes(feature.attributes() + [ fid # clamp_angle(angle) ]) axis_sink.addFeature(new_feature) def write_segment(fid, points, feature): bend = Bend(points) new_feature = QgsFeature() new_feature.setGeometry(QgsGeometry.fromPolylineXY(points)) new_feature.setAttributes(feature.attributes() + [ fid, bend.npoints(), bend.length(), bend.wavelength(), bend.sinuosity(), bend.amplitude(), bend.omega_origin(), bend.omega_end() # bend.curvature_radius() ]) segment_sink.addFeature(new_feature) stem, stem_idx = bend.max_amplitude_stem() if stem is None: # ProcessingLog.addToLog(ProcessingLog.LOG_INFO, str(points)) return stem_feature = QgsFeature() stem_feature.setGeometry(stem) stem_feature.setAttributes([fid, stem.length()]) stem_sink.addFeature(stem_feature) def write_inflection_point(point_id, point, angle, interdistance): new_feature = QgsFeature() new_feature.setGeometry(QgsGeometry.fromPointXY(point)) new_feature.setAttributes([point_id, angle, interdistance]) inflection_sink.addFeature(new_feature) total = 100.0 / layer.featureCount() if layer.featureCount() else 0 fid = 0 point_id = 0 # Total count of detected inflection points detected = 0 # Total count of retained inflection points retained = 0 for current, feature in enumerate(layer.getFeatures()): points = feature.geometry().asPolyline() points_iterator = iter(points) a = next(points_iterator) b = next(points_iterator) current_sign = 0 current_segment = [a] current_axis_direction = None bends = list() inflection_points = list() def axis_angle(entry): if entry.previous is None or entry.next is None: return 0.0 a = inflection_points[entry.previous] b = inflection_points[entry.index] c = inflection_points[entry.next] ab = qgs_vector(a, b) bc = qgs_vector(b, c) return clamp_angle(math.degrees(ab.angle(bc))) def interdistance(entry): l1 = entry.previous and qgs_vector( inflection_points[entry.previous], inflection_points[entry.index]).length() or 0.0 l2 = entry.next and qgs_vector( inflection_points[entry.index], inflection_points[entry.next]).length() or 0.0 return l1 + l2 # write_inflection_point(point_id, a) point_id = point_id + 1 for c in points_iterator: sign = angle_sign(a, b, c) if current_sign * sign < 0: p0 = current_segment[0] pi = QgsPointXY(0.5 * (a.x() + b.x()), 0.5 * (a.y() + b.y())) current_segment.append(pi) if current_axis_direction: angle = current_axis_direction.angle(qgs_vector( p0, pi)) * 180 / math.pi else: angle = 0.0 # write_axis_segment(fid, p0, pi, feature, angle) # write_segment(fid, current_segment, feature) # write_inflection_point(point_id, pi) bend = Bend(current_segment) bends.append(bend) inflection_points.append(p0) current_sign = sign current_segment = [pi, b] current_axis_direction = qgs_vector(p0, pi) fid = fid + 1 point_id = point_id + 1 else: current_segment.append(b) if current_sign == 0: current_sign = sign a, b = b, c p0 = current_segment[0] if current_axis_direction: angle = current_axis_direction.angle(qgs_vector( p0, b)) * 180 / math.pi else: angle = 0.0 # write_axis_segment(fid, p0, b, feature, angle) current_segment.append(b) # write_segment(fid, current_segment, feature) # write_inflection_point(point_id, b) bend = Bend(current_segment) bends.append(bend) inflection_points.append(p0) inflection_points.append(b) fid = fid + 1 point_id = point_id + 1 detected = detected + len(inflection_points) # ProcessingLog.addToLog(ProcessingLog.LOG_INFO, 'Inflections points = %d' % len(inflection_points)) # ProcessingLog.addToLog(ProcessingLog.LOG_INFO, 'Bends = %d' % len(bends)) # Filter out smaller bends entries = list() entry = QueueEntry(0) entry.previous = None entry.next = 1 entry.priority = float('inf') entry.interdistance = float('inf') entries.append(entry) for k in range(1, len(inflection_points) - 1): entry = QueueEntry(k) entry.previous = k - 1 entry.next = k + 1 entry.priority = bends[k - 1].amplitude() + bends[k].amplitude() entry.interdistance = qgs_vector(inflection_points[k-1], inflection_points[k]).length() + \ qgs_vector(inflection_points[k], inflection_points[k+1]).length() entries.append(entry) k = len(inflection_points) - 1 entry = QueueEntry(k) entry.previous = k - 1 entry.next = None entry.priority = float('inf') entry.interdistance = float('inf') entries.append(entry) queue = list(entries) heapify(queue) while queue: entry = heappop(queue) k = entry.index # ProcessingLog.addToLog(ProcessingLog.LOG_INFO, 'Entry = %s' % entry) if entry.priority > 2 * resolution: break if entry.duplicate or entry.removed: continue if entry.interdistance > lmax: continue previous_entry = entries[entry.previous] next_entry = entries[entry.next] if previous_entry.previous is None: continue if next_entry.next is None: continue new_entry = QueueEntry(k) entries[previous_entry.previous].next = k new_entry.previous = previous_entry.previous entries[next_entry.next].previous = k new_entry.next = next_entry.next before_bend = Bend.merge(bends[new_entry.previous], bends[entry.previous]) after_bend = Bend.merge(bends[k], bends[entry.next]) bends[new_entry.previous] = before_bend bends[k] = after_bend new_entry.priority = before_bend.amplitude( ) + after_bend.amplitude() new_entry.interdistance = qgs_vector(inflection_points[new_entry.previous], inflection_points[k]).length() + \ qgs_vector(inflection_points[k], inflection_points[new_entry.next]).length() heappush(queue, new_entry) entries[k] = new_entry previous_entry.removed = True next_entry.removed = True entry.duplicate = True # Filter out large axis angles # if max_angle > 0.0: # index = 0 # queue = list() # while True: # entry = entries[index] # if entry.previous is None or entry.next is None: # entry.priority = 0.0 # queue.append(entry) # if entry.next is None: # break # else: # index = entry.next # continue # # -priority to sort entries from large to small angle # entry.priority = -abs(axis_angle(entry)) # queue.append(entry) # index = entry.next # heapify(queue) # while queue: # entry = heappop(queue) # angle = -entry.priority # if angle < max_angle: # break # if entry.duplicate or entry.removed: # continue # if entry.previous is None or entry.next is None: # continue # if entry.interdistance > 2*lmax: # continue # previous_entry = entries[entry.previous] # next_entry = entries[entry.next] # merged_bend = Bend.merge(bends[entry.previous], bends[entry.index]) # bends[entry.previous] = merged_bend # previous_entry.duplicate = True # new_previous_entry = QueueEntry(previous_entry.index) # new_previous_entry.previous = previous_entry.previous # new_previous_entry.next = entry.next # angle = axis_angle(new_previous_entry) # dist = new_previous_entry.interdistance = interdistance(new_previous_entry) # new_previous_entry.priority = -abs(angle) # entries[new_previous_entry.index] = new_previous_entry # heappush(queue, new_previous_entry) # next_entry.duplicate = True # new_next_entry = QueueEntry(next_entry.index) # new_next_entry.previous = entry.previous # new_next_entry.next = next_entry.next # angle = axis_angle(new_next_entry) # dist = new_next_entry.interdistance = interdistance(new_next_entry) # new_next_entry.priority = -abs(angle) # entries[new_next_entry.index] = new_next_entry # heappush(queue, new_next_entry) # entry.removed = True # Output results index = 0 while True: entry = entries[index] point = inflection_points[index] if entry.next is None: point_id = point_id + 1 angle = axis_angle(entry) dist = interdistance(entry) write_inflection_point(point_id, point, angle, dist) retained = retained + 1 break bend = bends[index] point_id = point_id + 1 fid = fid + 1 # ProcessingLog.addToLog(ProcessingLog.LOG_INFO, 'Points = %s' % bend.points) angle = axis_angle(entry) dist = interdistance(entry) write_inflection_point(point_id, point, angle, dist) retained = retained + 1 write_axis_segment(fid, bend.p_origin, bend.p_end, feature) write_segment(fid, bend.points, feature) index = entry.next feedback.setProgress(int(current * total)) feedback.pushInfo('Retained inflection points = %d / %d' % (retained, detected)) return { self.OUTPUT: segment_id, self.FLOW_AXIS: axis_id, self.INFLECTION_POINTS: inflection_id, self.STEMS: stem_id }
def make_features_compatible(new_features, input_layer): """Try to make the new features compatible with old features by: - converting single to multi part - dropping additional attributes - adding back M/Z values - drop Z/M - convert multi part to single part :param new_features: new features :type new_features: list of QgsFeatures :param input_layer: input layer :type input_layer: QgsVectorLayer :return: modified features :rtype: list of QgsFeatures """ input_wkb_type = input_layer.wkbType() result_features = [] for new_f in new_features: # Fix attributes if new_f.fields().count() > 0: attributes = [] for field in input_layer.fields(): if new_f.fields().indexFromName(field.name()) >= 0: attributes.append(new_f[field.name()]) else: attributes.append(None) f = QgsFeature(input_layer.fields()) f.setAttributes(attributes) f.setGeometry(new_f.geometry()) new_f = f else: lendiff = len(new_f.attributes()) - len(input_layer.fields()) if lendiff > 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) f.setAttributes(new_f.attributes()[:len(input_layer.fields())]) new_f = f elif lendiff < 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) attributes = new_f.attributes() + [ None for i in range(-lendiff) ] f.setAttributes(attributes) new_f = f # Check if we need geometry manipulation new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType()) new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry) input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown) # Drop geometry if layer is geometry-less if not input_layer_has_geom and new_f_has_geom: f = QgsFeature(input_layer.fields()) f.setAttributes(new_f.attributes()) new_f = f result_features.append(new_f) continue # skip the rest if input_layer_has_geom and new_f_has_geom and \ new_f.geometry().wkbType() != input_wkb_type: # Fix geometry # Single -> Multi if (QgsWkbTypes.isMultiType(input_wkb_type) and not new_f.geometry().isMultipart()): new_geom = new_f.geometry() new_geom.convertToMultiType() new_f.setGeometry(new_geom) # Drop Z/M if (new_f.geometry().constGet().is3D() and not QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropZValue() new_f.setGeometry(new_geom) if (new_f.geometry().constGet().isMeasure() and not QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropMValue() new_f.setGeometry(new_geom) # Add Z/M back (set it to 0) if (not new_f.geometry().constGet().is3D() and QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addZValue(0.0) new_f.setGeometry(new_geom) if (not new_f.geometry().constGet().isMeasure() and QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addMValue(0.0) new_f.setGeometry(new_geom) # Multi -> Single if (not QgsWkbTypes.isMultiType(input_wkb_type) and new_f.geometry().isMultipart()): g = new_f.geometry() g2 = g.constGet() for i in range(g2.partCount()): # Clone or crash! g4 = QgsGeometry(g2.geometryN(i).clone()) f = QgsVectorLayerUtils.createFeature( input_layer, g4, { i: new_f.attribute(i) for i in range(new_f.fields().count()) }) result_features.append(f) else: result_features.append(new_f) else: result_features.append(new_f) return result_features
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) source_fields_parameter = self.parameterAsFields( parameters, self.INPUT_FIELD, context) target = self.parameterAsVectorLayer(parameters, self.OUTPUT, context) target_fields_parameter = self.parameterAsFields( parameters, self.OUTPUT_FIELD, context) action_on_duplicate = self.parameterAsEnum(parameters, self.ACTION_ON_DUPLICATE, context) results = { self.OUTPUT: None, self.APPENDED_COUNT: None, self.UPDATED_COUNT: None, self.SKIPPED_COUNT: None } target_value_dict = dict() source_field_unique_values = '' target_field_unique_values = '' source_field_type = None target_field_type = None if source_fields_parameter: source_field_unique_values = source_fields_parameter[0] source_field_type = source.fields().field( source_field_unique_values).type() if target_fields_parameter: target_field_unique_values = target_fields_parameter[0] target_field_type = target.fields().field( target_field_unique_values).type() if source_field_type != target_field_type: feedback.pushInfo( "\nWARNING: Source and target fields to compare have different field types." ) if source_field_unique_values and target_field_unique_values and action_on_duplicate == self.NO_ACTION: feedback.reportError( "\nWARNING: Since you have chosen source and target fields to compare, you need to choose a valid action to apply on duplicate features before running this algorithm." ) return results if action_on_duplicate != self.NO_ACTION and not ( source_field_unique_values and target_field_unique_values): feedback.reportError( "\nWARNING: Since you have chosen an action on duplicate features, you need to choose both source and target fields for comparing values before running this algorithm." ) return results caps = target.dataProvider().capabilities() if not (caps & QgsVectorDataProvider.AddFeatures): feedback.reportError( "\nWARNING: The target layer does not support appending features to it! Choose another target layer." ) return results if action_on_duplicate == self.UPDATE_EXISTING_FEATURE and not ( caps & QgsVectorDataProvider.ChangeAttributeValues and caps & QgsVectorDataProvider.ChangeGeometries): feedback.reportError( "\nWARNING: The target layer does not support updating its features! Choose another action for duplicate features or choose another target layer." ) return results editable_before = False if target.isEditable(): editable_before = True feedback.reportError( "\nWARNING: You need to close the edit session on layer '{}' before running this algorithm." .format(target.name())) return results # Define a mapping between source and target layer mapping = dict() for target_idx in target.fields().allAttributesList(): target_field = target.fields().field(target_idx) source_idx = source.fields().indexOf(target_field.name()) if source_idx != -1: mapping[target_idx] = source_idx # Build dict of target field values so that we can search easily later {value1: [id1, id2], ...} if target_field_unique_values: for f in target.getFeatures(): if f[target_field_unique_values] in target_value_dict: target_value_dict[f[target_field_unique_values]].append( int(f.id())) else: target_value_dict[f[target_field_unique_values]] = [ int(f.id()) ] # Prepare features for the Copy and Paste results[self.APPENDED_COUNT] = 0 total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() destType = target.geometryType() destIsMulti = QgsWkbTypes.isMultiType(target.wkbType()) new_features = list() updated_features = dict() updated_geometries = dict() updated_features_count = 0 updated_geometries_count = 0 skipped_features_count = 0 # To properly count features that were skipped duplicate_features_set = set( ) # To properly count features that were updated for current, in_feature in enumerate(features): if feedback.isCanceled(): break target_feature_exists = False duplicate_target_value = None # If skip is the action, skip as soon as possible if source_field_unique_values: duplicate_target, duplicate_target_value = self.find_duplicate_value( in_feature[source_field_unique_values], source_field_type, target_value_dict, target_field_type) if duplicate_target: if action_on_duplicate == self.SKIP_FEATURE: request = QgsFeatureRequest( target_value_dict[duplicate_target_value] ) # Get target feature ids request.setFlags(QgsFeatureRequest.NoGeometry) request.setSubsetOfAttributes( []) # Note: this adds a new flag skipped_features_count += len( [f for f in target.getFeatures(request)]) continue target_feature_exists = True attrs = { target_idx: in_feature[source_idx] for target_idx, source_idx in mapping.items() } geom = QgsGeometry() if in_feature.hasGeometry() and target.isSpatial(): # Convert geometry to match destination layer # Adapted from QGIS qgisapp.cpp, pasteFromClipboard() geom = in_feature.geometry() if not geom.isNull(): if destType != QgsWkbTypes.UnknownGeometry: newGeometry = geom.convertToType(destType, destIsMulti) if newGeometry.isNull(): continue # Couldn't convert geom = newGeometry # Avoid intersection if enabled in digitize settings geom.avoidIntersections( QgsProject.instance().avoidIntersectionsLayers()) if target_feature_exists and action_on_duplicate == self.UPDATE_EXISTING_FEATURE: for t_f in target.getFeatures( target_value_dict[duplicate_target_value]): duplicate_features_set.add(t_f.id()) updated_features[t_f.id()] = attrs if target.isSpatial(): updated_geometries[t_f.id()] = geom else: # Append new_feature = QgsVectorLayerUtils().createFeature( target, geom, attrs) new_features.append(new_feature) feedback.setProgress(int(current * total)) # Do the Copy and Paste res_add_features = False try: with edit(target): target.beginEditCommand("Appending/Updating features...") if updated_features: for k, v in updated_features.items(): if target.changeAttributeValues(k, v): updated_features_count += 1 else: feedback.reportError( "\nERROR: Target feature (id={}) couldn't be updated to the following attributes: {}." .format(k, v)) if updated_geometries: for k, v in updated_geometries.items(): if target.changeGeometry(k, v): updated_geometries_count += 1 else: feedback.reportError( "\nERROR: Target feature's geometry (id={}) couldn't be updated." .format(k)) if new_features: res_add_features = target.addFeatures(new_features) target.endEditCommand() except QgsEditError as e: if not editable_before: # Let's close the edit session to prepare for a next run target.rollBack() feedback.reportError( "\nERROR: No features could be appended/updated to/in '{}', because of the following error:\n{}\n" .format(target.name(), repr(e))) return results if action_on_duplicate == self.SKIP_FEATURE: feedback.pushInfo( "\nSKIPPED FEATURES: {} duplicate features were skipped while copying features to '{}'!" .format(skipped_features_count, target.name())) results[self.SKIPPED_COUNT] = skipped_features_count if action_on_duplicate == self.UPDATE_EXISTING_FEATURE: feedback.pushInfo( "\nUPDATED FEATURES: {} out of {} duplicate features were updated while copying features to '{}'!" .format(updated_features_count, len(duplicate_features_set), target.name())) results[self.UPDATED_COUNT] = updated_features_count if not new_features: feedback.pushInfo( "\nFINISHED WITHOUT APPENDED FEATURES: There were no features to append to '{}'." .format(target.name())) else: if res_add_features: feedback.pushInfo( "\nAPPENDED FEATURES: {} out of {} features from input layer were successfully appended to '{}'!" .format(len(new_features), source.featureCount(), target.name())) results[self.APPENDED_COUNT] = len(new_features) else: # TODO do we really need this else message below? feedback.reportError( "\nERROR: The {} features from input layer could not be appended to '{}'. Sometimes this might be due to NOT NULL constraints that are not met." .format(source.featureCount(), target.name())) results[self.OUTPUT] = target return results
def add_topological_vertices(self, layer1, layer2, id_field=ID_FIELD): """ Modify layer1 adding vertices that are in layer2 and not in layer1 Ideally, we could pass the whole layer2 as parameter for addTopologicalPoints or, at least, pass one multi-geometry containing all geometries from layer2. However, onthe one side, the addTopologicalPoints function doesn't support a layer as parameter and, on the other side, there is a bug in the function that doesn't let it work with multi-geometries. That's why we need to traverse the whole layer2 in search for its individual geometries. We do use a SpatialIndex nonetheless, to improve efficiency. """ if QgsWkbTypes.isMultiType(layer2.wkbType()): layer2 = processing.run("native:multiparttosingleparts", { 'INPUT': layer2, 'OUTPUT': 'memory:' })['OUTPUT'] if layer2.geometryType() == QgsWkbTypes.PolygonGeometry: layer2 = processing.run("ladm_col:polygonstolines", { 'INPUT': layer2, 'OUTPUT': 'memory:' })['OUTPUT'] geom_added = list() index = QgsSpatialIndex(layer2) dict_features_layer2 = None candidate_features = None id_field_idx1 = layer1.fields().indexFromName(id_field) request1 = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx1]) id_field_idx2 = layer2.fields().indexFromName(id_field) request2 = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx2]) with edit(layer1): edit_layer = QgsVectorLayerEditUtils(layer1) dict_features_layer2 = { feature.id(): feature for feature in layer2.getFeatures(request2) } for feature in layer1.getFeatures(request1): bbox = feature.geometry().boundingBox() candidate_ids = index.intersects(bbox) candidate_features = [ dict_features_layer2[candidate_id] for candidate_id in candidate_ids ] intersect_features = list() for candidate_feature in candidate_features: if candidate_feature.geometry().intersects( feature.geometry()): intersect_features.append(candidate_feature) for intersect_feature in intersect_features: if intersect_feature.id() not in geom_added: edit_layer.addTopologicalPoints( intersect_feature.geometry()) geom_added.append(intersect_feature.id()) # free up memory del candidate_features del dict_features_layer2 gc.collect()
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri( self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri( self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue( self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fieldList, layerA.wkbType(), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) multiGeoms = 0 # how many multi geometries were encountered for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() if inGeom.isMultipart(): multiGeoms += 1 # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined: # 1) add both new parts as new features # 2) store one part as a new feature and the other one as part of the multi geometry # 2a) which part should be which, seems arbitrary else: attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine( inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine( splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if split_geom_engine.intersects( inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints( splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry( splitterPList, False) except: ProcessingLog.addToLog( ProcessingLog.LOG_WARNING, self. tr('Geometry exception while splitting' )) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints( inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged QgsMessageLog.logMessage( "appending") outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: QgsMessageLog.logMessage( "appending else") outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry \ and not QgsWkbTypes.isMultiType(aGeom.wkbType()): passed = len(aGeom.asPolyline()) > 2 if not passed: passed = (len(aGeom.asPolyline()) == 2 and aGeom.asPolyline()[0] != aGeom.asPolyline()[1]) # sometimes splitting results in lines of zero length if passed: outFeat.setGeometry(aGeom) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) if multiGeoms > 0: ProcessingLog.addToLog( ProcessingLog.LOG_INFO, self. tr('Feature geometry error: %s input features ignored due to multi-geometry.' ) % str(multiGeoms)) del writer
def testWriteShapefileWithSingleConversion(self): """Check writing geometries from a POLYGON ESRI shapefile does not convert to multi when "forceSinglePartGeometryType" options is TRUE also checks failing cases. OGR provider always report MULTI for POLYGON and LINESTRING, but if we set the import option "forceSinglePartGeometryType" the writer must respect the actual single-part type if the features in the data provider are actually single and not multi. """ ml = QgsVectorLayer(('Polygon?crs=epsg:4326&field=id:int'), 'test', 'memory') provider = ml.dataProvider() ft = QgsFeature() ft.setGeometry( QgsGeometry.fromWkt('Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))')) ft.setAttributes([1]) res, features = provider.addFeatures([ft]) dest_file_name = os.path.join(self.basetestpath, 'multipart.shp') write_result, error_message = QgsVectorLayerExporter.exportLayer( ml, dest_file_name, 'ogr', ml.crs(), False, {"driverName": "ESRI Shapefile"}) self.assertEqual(write_result, QgsVectorLayerExporter.NoError, error_message) # Open the newly created layer shapefile_layer = QgsVectorLayer(dest_file_name) dest_singlepart_file_name = os.path.join(self.basetestpath, 'singlepart.gpkg') write_result, error_message = QgsVectorLayerExporter.exportLayer( shapefile_layer, dest_singlepart_file_name, 'ogr', shapefile_layer.crs(), False, { "forceSinglePartGeometryType": True, "driverName": "GPKG", }) self.assertEqual(write_result, QgsVectorLayerExporter.NoError, error_message) # Load result layer and check that it's NOT MULTI single_layer = QgsVectorLayer(dest_singlepart_file_name) self.assertTrue(single_layer.isValid()) self.assertTrue(QgsWkbTypes.isSingleType(single_layer.wkbType())) # Now save the shapfile layer into a gpkg with no force options dest_multipart_file_name = os.path.join(self.basetestpath, 'multipart.gpkg') write_result, error_message = QgsVectorLayerExporter.exportLayer( shapefile_layer, dest_multipart_file_name, 'ogr', shapefile_layer.crs(), False, { "forceSinglePartGeometryType": False, "driverName": "GPKG", }) self.assertEqual(write_result, QgsVectorLayerExporter.NoError, error_message) # Load result layer and check that it's MULTI multi_layer = QgsVectorLayer(dest_multipart_file_name) self.assertTrue(multi_layer.isValid()) self.assertTrue(QgsWkbTypes.isMultiType(multi_layer.wkbType())) # Failing case: add a real multi to the shapefile and try to force to single self.assertTrue(shapefile_layer.startEditing()) ft = QgsFeature() ft.setGeometry( QgsGeometry.fromWkt( 'MultiPolygon (((0 0, 0 1, 1 1, 1 0, 0 0)), ((-10 -10,-10 -9,-9 -9,-10 -10)))' )) ft.setAttributes([2]) self.assertTrue(shapefile_layer.addFeatures([ft])) self.assertTrue(shapefile_layer.commitChanges()) dest_multipart_failure_file_name = os.path.join( self.basetestpath, 'multipart_failure.gpkg') write_result, error_message = QgsVectorLayerExporter.exportLayer( shapefile_layer, dest_multipart_failure_file_name, 'ogr', shapefile_layer.crs(), False, { "forceSinglePartGeometryType": True, "driverName": "GPKG", }) self.assertTrue(QgsWkbTypes.isMultiType(multi_layer.wkbType())) self.assertEqual( write_result, QgsVectorLayerExporter.ErrFeatureWriteFailed, "Failed to transform a feature with ID '1' to single part. Writing stopped." )