def processAlgorithm(self, parameters, context, feedback): if parameters[self.INPUT] == parameters[self.HUBS]: raise QgsProcessingException( self.tr('Same layer given for both hubs and spokes')) point_source = self.parameterAsSource(parameters, self.INPUT, context) hub_source = self.parameterAsSource(parameters, self.HUBS, context) fieldName = self.parameterAsString(parameters, self.FIELD, context) units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT, context)] fields = point_source.fields() fields.append(QgsField('HubName', QVariant.String)) fields.append(QgsField('HubDist', QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, point_source.sourceCrs()) index = QgsSpatialIndex(hub_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(point_source.sourceCrs(), context.transformContext()))) distance = QgsDistanceArea() distance.setSourceCrs(point_source.sourceCrs(), context.transformContext()) distance.setEllipsoid(context.project().ellipsoid()) # Scan source points, find nearest hub, and write to output file features = point_source.getFeatures() total = 100.0 / point_source.featureCount() if point_source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): sink.addFeature(f, QgsFeatureSink.FastInsert) continue src = f.geometry().boundingBox().center() neighbors = index.nearestNeighbor(src, 1) ft = next(hub_source.getFeatures(QgsFeatureRequest().setFilterFid(neighbors[0]).setSubsetOfAttributes([fieldName], hub_source.fields()).setDestinationCrs(point_source.sourceCrs(), context.transformContext()))) closest = ft.geometry().boundingBox().center() hubDist = distance.measureLine(src, closest) if units != self.LAYER_UNITS: hub_dist_in_desired_units = distance.convertLengthMeasurement(hubDist, units) else: hub_dist_in_desired_units = hubDist attributes = f.attributes() attributes.append(ft[fieldName]) attributes.append(hub_dist_in_desired_units) feat = QgsFeature() feat.setAttributes(attributes) feat.setGeometry(QgsGeometry.fromPointXY(src)) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
class LayerIndex: def __init__(self, layer): self.__layer = layer self.__index = QgsSpatialIndex() feats = vector.features(layer) for ft in feats: self.__index.insertFeature(ft) def contains(self, point): """Return true if the point intersects the layer""" intersects = self.__index.intersects(point.boundingBox()) for i in intersects: request = QgsFeatureRequest().setFilterFid(i) feat = self.__layer.getFeatures(request).next() tmpGeom = QgsGeometry(feat.geometry()) if point.intersects(tmpGeom): return True return False def countIntersection(self,bufferGeom,nb): """Return true if the buffer intersects enough entities""" count = 0 intersects = self.__index.intersects(bufferGeom.boundingBox()) for i in intersects: request = QgsFeatureRequest().setFilterFid(i) feat = self.__layer.getFeatures(request).next() tmpGeom = QgsGeometry(feat.geometry()) if bufferGeom.intersects(tmpGeom): count += 1 if count >= nb: return True return False
def createIndex(provider): feat = QgsFeature() index = QgsSpatialIndex() fit = provider.getFeatures() while fit.nextFeature(feat): index.insertFeature(feat) return index
def compute(self, line1, line2, field1, field2, outPath, matType, nearest, progressBar): layer1 = ftools_utils.getVectorLayerByName(line1) layer2 = ftools_utils.getVectorLayerByName(line2) if layer1.id() == layer2.id(): if nearest > 0: nearest = nearest + 1 provider1 = layer1.dataProvider() provider2 = layer2.dataProvider() sindex = QgsSpatialIndex() inFeat = QgsFeature() fit2 = provider2.getFeatures() while fit2.nextFeature(inFeat): sindex.insertFeature(inFeat) if nearest < 1: nearest = layer2.featureCount() else: nearest = nearest index1 = provider1.fieldNameIndex(field1) index2 = provider2.fieldNameIndex(field2) distArea = QgsDistanceArea() #use srs of the first layer (users should ensure that they are both in the same projection) # sRs = provider1.crs() # distArea.setSourceSRS(sRs) f = open(unicode(outPath), "wb") writer = UnicodeWriter(f) if matType != "Standard": if matType == "Linear": writer.writerow(["InputID", "TargetID", "Distance"]) else: writer.writerow(["InputID", "MEAN", "STDDEV", "MIN", "MAX"]) self.linearMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar) else: self.regularMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar) f.close()
def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments): self.vbands = [] self.hbands = [] self.vidx = QgsSpatialIndex() self.hidx = QgsSpatialIndex() xres = (xmax - xmin) / x_segments yres = (ymax - ymin) / y_segments self.xmin, self.ymax, self.xres, self.yres = xmin, ymax, xres, yres def addVBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.vbands.append(f) self.vidx.insertFeature(f) def addHBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.hbands.append(f) self.hidx.insertFeature(f) for x in range(x_segments): addVBand(x, QgsGeometry.fromRect(QgsRectangle(xmin + x * xres, ymin, xmin + (x + 1) * xres, ymax))) for y in range(y_segments): addHBand(y, QgsGeometry.fromRect(QgsRectangle(xmin, ymax - (y + 1) * yres, xmax, ymax - y * yres)))
def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments): self.vbands = [] self.hbands = [] self.vidx = QgsSpatialIndex() self.hidx = QgsSpatialIndex() xres = (xmax - xmin) / x_segments yres = (ymax - ymin) / y_segments self.xmin, self.ymax, self.xres, self.yres = xmin, ymax, xres, yres def addVBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.vbands.append(f) self.vidx.insertFeature(f) def addHBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.hbands.append(f) self.hidx.insertFeature(f) for x in range(x_segments): pt0 = QgsPoint(xmin + x * xres, ymax) pt1 = QgsPoint(xmin + x * xres, ymin) pt2 = QgsPoint(xmin + (x + 1) * xres, ymin) pt3 = QgsPoint(xmin + (x + 1) * xres, ymax) addVBand(x, QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]])) for y in range(y_segments): pt0 = QgsPoint(xmin, ymax - y * yres) pt1 = QgsPoint(xmin, ymax - (y + 1) * yres) pt2 = QgsPoint(xmax, ymax - (y + 1) * yres) pt3 = QgsPoint(xmax, ymax - y * yres) addHBand(y, QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]]))
def testIndex(self): idx = QgsSpatialIndex() fid = 0 for y in range(5, 15, 5): for x in range(5, 25, 5): ft = QgsFeature() ft.setFeatureId(fid) ft.setGeometry(QgsGeometry.fromPoint(QgsPoint(x, y))) idx.insertFeature(ft) fid += 1 # intersection test rect = QgsRectangle(7.0, 3.0, 17.0, 13.0) fids = idx.intersects(rect) myExpectedValue = 4 myValue = len(fids) myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myValue) self.assertEqual(myValue, myExpectedValue, myMessage) fids.sort() myMessage = ('Expected: %s\nGot: %s\n' % ([1, 2, 5, 6], fids)) assert fids == [1, 2, 5, 6], myMessage # nearest neighbor test fids = idx.nearestNeighbor(QgsPoint(8.75, 6.25), 3) myExpectedValue = 0 myValue = len(fids) myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myValue) fids.sort() myMessage = ('Expected: %s\nGot: %s\n' % ([0, 1, 5], fids)) assert fids == [0, 1, 5], myMessage
def poly2nb(self): lst = [] index = QgsSpatialIndex() featsA = self.lyr.getFeatures() featsB = self.lyr.getFeatures() for ft in featsA: index.insertFeature(ft) featB = QgsFeature() prv = self.lyr.dataProvider() while featsB.nextFeature(featB): geomB = featB.constGeometry() idb = featB.id() idxs = index.intersects(geomB.boundingBox()) sor = [] for idx in idxs: rqst = QgsFeatureRequest().setFilterFid(idx) featA = prv.getFeatures(rqst).next() ida = featA.id() geomA = QgsGeometry(featA.geometry()) if idb!=ida: if geomB.touches(geomA)==True: sor.append(ida) lst.append(sor) return lst
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) bbox = source.sourceExtent() sourceIndex = QgsSpatialIndex(source, feedback) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(p, index, minDistance, points): request = QgsFeatureRequest().setFilterFids(ids).setSubsetOfAttributes([]) for f in source.getFeatures(request): if feedback.isCanceled(): break tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo(self.tr('Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) return {self.OUTPUT: dest_id}
def spatialindex(layer): """Creates a spatial index for the passed vector layer. """ idx = QgsSpatialIndex() feats = features(layer) for ft in feats: idx.insertFeature(ft) return idx
def get_spatial_index(data_provider): """Create spatial index from a data provider.""" qgs_feature = QgsFeature() index = QgsSpatialIndex() qgs_features = data_provider.getFeatures() while qgs_features.nextFeature(qgs_feature): index.insertFeature(qgs_feature) return index
def processAlgorithm(self, feedback): pointCount = int(self.getParameterValue(self.POINT_NUMBER)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) extent = str(self.getParameterValue(self.EXTENT)).split(',') crsId = self.getParameterValue(self.CRS) crs = QgsCoordinateReferenceSystem() crs.createFromUserInput(crsId) xMin = float(extent[0]) xMax = float(extent[1]) yMin = float(extent[2]) yMax = float(extent[3]) extent = QgsGeometry().fromRect( QgsRectangle(xMin, yMin, xMax, yMax)) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QgsWkbTypes.Point, crs) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = xMin + (xMax - xMin) * random.random() ry = yMin + (yMax - yMin) * random.random() pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(extent) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) del writer
def buildReferenceIndex(self, segments): refDict = {} index = QgsSpatialIndex() for i, segment in enumerate(segments): refDict[i] = segment feature = QgsFeature(i) feature.setGeometry(segment) index.insertFeature(feature) return refDict, index
def processAlgorithm(self, progress): layer = dataobjects.getObjectFromUri( self.getParameterValue(self.VECTOR)) pointCount = int(self.getParameterValue(self.POINT_NUMBER)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) bbox = layer.extent() idxLayer = vector.spatialindex(layer) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QgsWkbTypes.Point, layer.crs()) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount index = QgsSpatialIndex() points = dict() request = QgsFeatureRequest() random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) ids = idxLayer.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(pnt, index, minDistance, points): for i in ids: f = next(layer.getFeatures(request.setFilterFid(i))) tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 progress.setPercentage(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) del writer
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) spatialIndex = QgsSpatialIndex(source, feedback) distance = QgsDistanceArea() distance.setSourceCrs(source.sourceCrs(), context.transformContext()) distance.setEllipsoid(context.project().ellipsoid()) sumDist = 0.00 A = source.sourceExtent() A = float(A.width() * A.height()) features = source.getFeatures() count = source.featureCount() total = 100.0 / count if count else 1 for current, feat in enumerate(features): if feedback.isCanceled(): break neighbourID = spatialIndex.nearestNeighbor( feat.geometry().asPoint(), 2)[1] request = QgsFeatureRequest().setFilterFid(neighbourID).setSubsetOfAttributes([]) neighbour = next(source.getFeatures(request)) sumDist += distance.measureLine(neighbour.geometry().asPoint(), feat.geometry().asPoint()) feedback.setProgress(int(current * total)) do = float(sumDist) / count de = float(0.5 / math.sqrt(count / A)) d = float(do / de) SE = float(0.26136 / math.sqrt(count ** 2 / A)) zscore = float((do - de) / SE) results = {} results[self.OBSERVED_MD] = do results[self.EXPECTED_MD] = de results[self.NN_INDEX] = d results[self.POINT_COUNT] = count results[self.Z_SCORE] = zscore if output_file: data = [] data.append('Observed mean distance: ' + str(do)) data.append('Expected mean distance: ' + str(de)) data.append('Nearest neighbour index: ' + str(d)) data.append('Number of points: ' + str(count)) data.append('Z-Score: ' + str(zscore)) self.createHTML(output_file, data) results[self.OUTPUT_HTML_FILE] = output_file return results
def __init__(self, layer): self.__layer = layer if QGis.QGIS_VERSION_INT >= 20700: self.__index = QgsSpatialIndex(layer.getFeatures()) else: self.__index = QgsSpatialIndex() for ft in layer.getFeatures(): self.__index.insertFeature(ft)
def processAlgorithm(self, parameters, context, feedback): pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) bbox = self.parameterAsExtent(parameters, self.EXTENT, context, crs) extent = QgsGeometry().fromRect(bbox) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, crs) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if geom.within(extent) and \ vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.addFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo(self.tr('Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): pointCount = int(self.getParameterValue(self.POINT_NUMBER)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) extent = unicode(self.getParameterValue(self.EXTENT)).split(',') xMin = float(extent[0]) xMax = float(extent[1]) yMin = float(extent[2]) yMax = float(extent[3]) extent = QgsGeometry().fromRect( QgsRectangle(xMin, yMin, xMax, yMax)) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) mapCRS = iface.mapCanvas().mapSettings().destinationCrs() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QGis.WKBPoint, mapCRS) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount > 0 else 1 index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = xMin + (xMax - xMin) * random.random() ry = yMin + (yMax - yMin) * random.random() pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(extent) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 progress.setPercentage(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) del writer
def createIndex( provider ): ''' return Spatial Index of line layer features @param provider: QgsDataProvider ''' feat = QgsFeature() index = QgsSpatialIndex() provider.rewind() provider.select() while provider.nextFeature( feat ): index.insertFeature( feat ) return index
def regularMatrix(self, parameters, context, source, inField, target_source, targetField, nPoints, feedback): distArea = QgsDistanceArea() distArea.setSourceCrs(source.sourceCrs(), context.transformContext()) distArea.setEllipsoid(context.project().ellipsoid()) inIdx = source.fields().lookupField(inField) targetIdx = target_source.fields().lookupField(targetField) index = QgsSpatialIndex(target_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(source.sourceCrs(), context.transformContext())), feedback) first = True sink = None dest_id = None features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([inIdx])) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break inGeom = inFeat.geometry() if first: featList = index.nearestNeighbor(inGeom.asPoint(), nPoints) first = False fields = QgsFields() input_id_field = source.fields()[inIdx] input_id_field.setName('ID') fields.append(input_id_field) for f in target_source.getFeatures(QgsFeatureRequest().setFilterFids(featList).setSubsetOfAttributes([targetIdx]).setDestinationCrs(source.sourceCrs(), context.transformContext())): fields.append(QgsField(str(f[targetField]), QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) data = [inFeat[inField]] for target in target_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setFilterFids(featList).setDestinationCrs(source.sourceCrs(), context.transformContext())): if feedback.isCanceled(): break outGeom = target.geometry() dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint()) data.append(dist) out_feature = QgsFeature() out_feature.setGeometry(inGeom) out_feature.setAttributes(data) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) pointCount = int(self.getParameterValue(self.POINT_NUMBER)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) bbox = layer.extent() idxLayer = QgsProcessingUtils.createSpatialIndex(layer, context) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPointXY(rx, ry) geom = QgsGeometry.fromPoint(pnt) ids = idxLayer.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(pnt, index, minDistance, points): request = QgsFeatureRequest().setFilterFids(ids).setSubsetOfAttributes([]) for f in layer.getFeatures(request): tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: QgsMessageLog.logMessage(self.tr('Can not generate requested number of random points. ' 'Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) del writer
class TriangleMesh: # 0 - 3 # | / | # 1 - 2 def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments): self.flen = 0 self.quadrangles = [] self.spatial_index = QgsSpatialIndex() xres = (xmax - xmin) / x_segments yres = (ymax - ymin) / y_segments for y in range(y_segments): for x in range(x_segments): pt0 = QgsPoint(xmin + x * xres, ymax - y * yres) pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres) pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres) pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres) self._addQuadrangle(pt0, pt1, pt2, pt3) def _addQuadrangle(self, pt0, pt1, pt2, pt3): f = QgsFeature(self.flen) f.setGeometry(QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]])) self.quadrangles.append(f) self.spatial_index.insertFeature(f) self.flen += 1 def intersects(self, geom): for fid in self.spatial_index.intersects(geom.boundingBox()): quad = self.quadrangles[fid].geometry() if quad.intersects(geom): yield quad def splitPolygons(self, geom): for quad in self.intersects(geom): pts = quad.asPolygon()[0] tris = [[[pts[0], pts[1], pts[3], pts[0]]], [[pts[3], pts[1], pts[2], pts[3]]]] if geom.contains(quad): yield tris[0] yield tris[1] else: for i, tri in enumerate(map(QgsGeometry.fromPolygon, tris)): if geom.contains(tri): yield tris[i] elif geom.intersects(tri): poly = geom.intersection(tri) if poly.isMultipart(): for sp in poly.asMultiPolygon(): yield sp else: yield poly.asPolygon()
def get_spatial_index(data_provider): """Create spatial index from a data provider. :param data_provider: QGIS data provider name .e.g.'ogr'. :type data_provider: str """ qgs_feature = QgsFeature() index = QgsSpatialIndex() # noinspection PyUnresolvedReferences qgs_features = data_provider.getFeatures() while qgs_features.nextFeature(qgs_feature): index.insertFeature(qgs_feature) return index
def compute_graph(features, feedback, create_id_graph=False, min_distance=0): """ compute topology from a layer/field """ s = Graph(sort_graph=False) id_graph = None if create_id_graph: id_graph = Graph(sort_graph=True) # skip features without geometry features_with_geometry = {f_id: f for (f_id, f) in features.items() if f.hasGeometry()} total = 70.0 / len(features_with_geometry) if features_with_geometry else 1 index = QgsSpatialIndex() i = 0 for feature_id, f in features_with_geometry.items(): if feedback.isCanceled(): break g = f.geometry() if min_distance > 0: g = g.buffer(min_distance, 5) engine = QgsGeometry.createGeometryEngine(g.constGet()) engine.prepareGeometry() feature_bounds = g.boundingBox() # grow bounds a little so we get touching features feature_bounds.grow(feature_bounds.width() * 0.01) intersections = index.intersects(feature_bounds) for l2 in intersections: f2 = features_with_geometry[l2] if engine.intersects(f2.geometry().constGet()): s.add_edge(f.id(), f2.id()) s.add_edge(f2.id(), f.id()) if id_graph: id_graph.add_edge(f.id(), f2.id()) index.insertFeature(f) i += 1 feedback.setProgress(int(i * total)) for feature_id, f in features_with_geometry.items(): if feedback.isCanceled(): break if feature_id not in s.node_edge: s.add_edge(feature_id, None) return s, id_graph
def populateIndex(self): """ Populates the spatial index """ #spatial index self.index = QgsSpatialIndex() for feat in self.reference.getFeatures(): self.index.insertFeature(feat)
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, sourceA.fields(), geomType, sourceA.sourceCrs()) featB = QgsFeature() outFeat = QgsFeature() indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break if featA.hasGeometry(): geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) else: sink.addFeature(featA, QgsFeatureSink.FastInsert) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def create_spatial_index(self, layer): spatial_index = QgsSpatialIndex() # features dictionary centroid_features = {} features = layer.getFeatures() for feature in features: # convert to point feature point_feature = QgsFeature(layer.fields()) point_feature.setId(feature.id()) point_feature.setAttributes(feature.attributes()) point_feature.setGeometry(feature.geometry().centroid()) centroid_features[point_feature.id()] = point_feature spatial_index.insertFeature(point_feature) return (spatial_index, centroid_features)
def __init__(self, referenceLayer): """ Constructor :param referenceLayer: QgsVectorLayer """ super(self.__class__,self).__init__() self.referenceLayer = referenceLayer # Build spatial index self.index = QgsSpatialIndex(self.referenceLayer.getFeatures())
def create_spatial_index(layer): """Helper function to create the spatial index on a vector layer. This function is mainly used to see the processing time with the decorator. :param layer: The vector layer. :type layer: QgsVectorLayer :return: The index. :rtype: QgsSpatialIndex """ request = QgsFeatureRequest().setSubsetOfAttributes([]) try: spatial_index = QgsSpatialIndex(layer.getFeatures(request)) except BaseException: # Spatial index is creating an unknown exception. # https://github.com/inasafe/inasafe/issues/4304 # or https://gitter.im/inasafe/inasafe?at=5a2903d487680e6230e0359a LOGGER.warning( 'An Exception has been raised from the spatial index creation. ' 'We will clean your layer and try again.') new_layer = clean_layer(layer) try: spatial_index = QgsSpatialIndex(new_layer.getFeatures()) except BaseException: # We got another exception. # We try now to insert feature by feature. # It's slower than the using the feature iterator. spatial_index = QgsSpatialIndex() for feature in new_layer.getFeatures(request): try: spatial_index.insertFeature(feature) except BaseException: LOGGER.critical( 'A feature has been removed from the spatial index.') # # We tried one time to clean the layer, we can't do more. # LOGGER.critical( # 'An Exception has been raised from the spatial index ' # 'creation. Unfortunately, we already try to clean your ' # 'layer. We will stop here the process.') # raise SpatialIndexCreationError return spatial_index
def confirm(self): # runs when OK button is pressed # initialise input parameters self.BASE_LAYER = self.dlg.ui.comboBase.currentItemData() self.SECONDARY_LAYER = self.dlg.ui.comboSecondary.currentItemData() self.SURFACE_LAYER = self.dlg.ui.comboSurface.currentItemData() self.LOCALITIES_LAYER = self.dlg.ui.comboLocalities.currentItemData() self.TAXON_FIELD_INDEX = self.dlg.ui.comboTaxonField.currentItemData()[0] self.GRID_LAYER = self.dlg.ui.comboGrid.currentItemData() self.X_MIN = float(self.dlg.ui.leMinX.text()) self.Y_MIN = float(self.dlg.ui.leMinY.text()) self.X_MAX = float(self.dlg.ui.leMaxX.text()) self.Y_MAX = float(self.dlg.ui.leMaxY.text()) self.OUT_WIDTH = self.dlg.ui.spnOutWidth.value() self.OUT_HEIGHT = self.dlg.ui.spnOutHeight.value() self.OUT_DIR = self.dlg.ui.leOutDir.text() try: self.getUniqueValues() except: message = "Could not get unique values from localities layer. " message += "Check that the localities layer and taxon identifier " message += "field are properly specified." QMessageBox.information(self.dlg,"Distribution Map Generator", message) return question = "This will generate " + str(self.UNIQUE_COUNT) question += " maps. Are you sure you want to continue?" reply = QMessageBox.question(self.dlg,'Distribution Map Generator', question, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) self.GRID_INDEX = QgsSpatialIndex() for feat in getLayerFromId(self.GRID_LAYER).getFeatures(): self.GRID_INDEX.insertFeature(feat) if reply == QMessageBox.Yes: try: self.process() except QgsCsException: return except Exception as ex: log(ex) return QMessageBox.information(self.dlg,"Distribution Map Generator", "Map processing complete.") self.dlg.ui.progressBar.setValue(0) QDialog.accept(self.dlg) else: return
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) features = source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([])) total = 100.0 / source.featureCount() if source.featureCount() else 0 geoms = dict() null_geom_features = set() index = QgsSpatialIndex() for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): null_geom_features.add(f.id()) continue geoms[f.id()] = f.geometry() index.addFeature(f) feedback.setProgress(int(0.10 * current * total)) # takes about 10% of time # start by assuming everything is unique, and chop away at this list unique_features = dict(geoms) current = 0 removed = 0 for feature_id, geometry in geoms.items(): if feedback.isCanceled(): break if feature_id not in unique_features: # feature was already marked as a duplicate continue candidates = index.intersects(geometry.boundingBox()) candidates.remove(feature_id) for candidate_id in candidates: if candidate_id not in unique_features: # candidate already marked as a duplicate (not sure if this is possible, # since it would mean the current feature would also have to be a duplicate! # but let's be safe!) continue if geometry.isGeosEqual(geoms[candidate_id]): # candidate is a duplicate of feature del unique_features[candidate_id] removed += 1 current += 1 feedback.setProgress(int(0.80 * current * total) + 10) # takes about 80% of time # now, fetch all the feature attributes for the unique features only # be super-smart and don't re-fetch geometries distinct_geoms = set(unique_features.keys()) output_feature_ids = distinct_geoms.union(null_geom_features) total = 100.0 / len(output_feature_ids) if output_feature_ids else 1 request = QgsFeatureRequest().setFilterFids( list(output_feature_ids)).setFlags(QgsFeatureRequest.NoGeometry) for current, f in enumerate(source.getFeatures(request)): if feedback.isCanceled(): break # use already fetched geometry if f.id() not in null_geom_features: f.setGeometry(unique_features[f.id()]) sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(0.10 * current * total) + 90) # takes about 10% of time feedback.pushInfo( self.tr('{} duplicate features removed'.format(removed))) return { self.OUTPUT: dest_id, self.DUPLICATE_COUNT: removed, self.RETAINED_COUNT: len(output_feature_ids) }
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) expression = QgsExpression( self.parameterAsString(parameters, self.EXPRESSION, context)) if expression.hasParserError(): raise QgsProcessingException(expression.parserErrorString()) expressionContext = self.createExpressionContext( parameters, context, source) expression.prepare(expressionContext) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs(), context.transformContext()) da.setEllipsoid(context.project().ellipsoid()) total = 100.0 / source.featureCount() if source.featureCount() else 0 current_progress = 0 for current, f in enumerate(source.getFeatures()): if feedback.isCanceled(): break if not f.hasGeometry(): continue current_progress = total * current feedback.setProgress(current_progress) expressionContext.setFeature(f) value = expression.evaluate(expressionContext) if expression.hasEvalError(): feedback.pushInfo( self.tr('Evaluation error for feature ID {}: {}').format( f.id(), expression.evalErrorString())) continue fGeom = f.geometry() engine = QgsGeometry.createGeometryEngine(fGeom.constGet()) engine.prepareGeometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measureArea(fGeom))) if pointCount == 0: feedback.pushInfo( "Skip feature {} as number of points for it is 0.".format( f.id())) continue index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 feature_total = total / pointCount if pointCount else 1 random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if engine.contains(geom.constGet()) and \ vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(current_progress + int(nPoints * feature_total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo( self.tr('Could not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) feedback.setProgress(100) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): if parameters[self.INPUT] == parameters[self.HUBS]: raise QgsProcessingException( self.tr('Same layer given for both hubs and spokes')) point_source = self.parameterAsSource(parameters, self.INPUT, context) if point_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) hub_source = self.parameterAsSource(parameters, self.HUBS, context) if hub_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.HUBS)) fieldName = self.parameterAsString(parameters, self.FIELD, context) units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT, context)] fields = point_source.fields() fields.append(QgsField('HubName', QVariant.String)) fields.append(QgsField('HubDist', QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, point_source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) index = QgsSpatialIndex( hub_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(point_source.sourceCrs(), context.transformContext()))) distance = QgsDistanceArea() distance.setSourceCrs(point_source.sourceCrs(), context.transformContext()) distance.setEllipsoid(context.project().ellipsoid()) # Scan source points, find nearest hub, and write to output file features = point_source.getFeatures() total = 100.0 / point_source.featureCount( ) if point_source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): sink.addFeature(f, QgsFeatureSink.FastInsert) continue src = f.geometry().boundingBox().center() neighbors = index.nearestNeighbor(src, 1) ft = next( hub_source.getFeatures(QgsFeatureRequest().setFilterFid( neighbors[0]).setSubsetOfAttributes( [fieldName], hub_source.fields()).setDestinationCrs( point_source.sourceCrs(), context.transformContext()))) closest = ft.geometry().boundingBox().center() hubDist = distance.measureLine(src, closest) if units != self.LAYER_UNITS: hub_dist_in_desired_units = distance.convertLengthMeasurement( hubDist, units) else: hub_dist_in_desired_units = hubDist attributes = f.attributes() attributes.append(ft[fieldName]) attributes.append(hub_dist_in_desired_units) feat = QgsFeature() feat.setAttributes(attributes) feat.setGeometry(QgsGeometry.fromPointXY(src)) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): # Get Parameters and assign to variable to work with layer = self.parameterAsLayer(parameters, self.SOURCE_LYR, context) idfield = self.parameterAsString(parameters, self.SOURCE_FIELD, context) idfield_index = layer.fields().indexFromName( idfield) # get the fieldindex of the id field idfield_type = layer.fields()[idfield_index].type( ) # get the fieldtype of this field attrfield = self.parameterAsString(parameters, self.ATTRIBUTE_FIELD, context) attrfield_index = layer.fields().indexFromName( attrfield) # get the fieldindex of the attribute field attrfield_type = layer.fields()[attrfield_index].type( ) # get the fieldtype of this field maxneighbors = self.parameterAsDouble(parameters, self.MAX_NEIGHBORS, context) maxdistance = self.parameterAsDouble(parameters, self.MAX_DISTANCE, context) donotcomparevalue = self.parameterAsDouble(parameters, self.DONOT_COMPARE_VALUE, context) donotcomparebool = self.parameterAsBool(parameters, self.DONOT_COMPARE_BOOL, context) op = self.parameterAsString(parameters, self.OPERATOR, context) op = int(op[0]) # get the index of the chosen operator #import operator ops = { # get the operator by this index 0: operator.lt, 1: operator.le, 2: operator.eq, 3: operator.ne, 4: operator.ge, 5: operator.gt } op_func = ops[op] # create the operator function total = 100.0 / layer.featureCount() if layer.featureCount( ) else 0 # Initialize progress for progressbar # if -1 has been chosen for maximum features to compare, use the amount of features of the layer, else use the given input if maxneighbors == -1: maxneighbors = layer.featureCount() fields = layer.fields() # get all fields of the inputlayer fields.append(QgsField( "near_id", idfield_type)) # create new field with same type as the inputfield fields.append(QgsField( "near_attr", attrfield_type)) # same here for the attribute field fields.append(QgsField("near_dist", QVariant.Double, len=20, prec=5)) # add a new field of type double idx = QgsSpatialIndex(layer.getFeatures()) # create a spatial index (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, layer.wkbType(), layer.sourceCrs()) for current, feat in enumerate( layer.getFeatures()): # iterate over source new_feat = QgsFeature(fields) # copy source fields + appended attridx = 0 # reset attribute fieldindex for attr in feat.attributes( ): # iterate over attributes of source layer for the current feature new_feat[ attridx] = attr # copy attribute values over to the new layer attridx += 1 # go to the next field new_feat.setGeometry(feat.geometry( )) # copy over the geometry of the source feature if ( (not (op_func(feat[attrfield], donotcomparevalue))) or (not donotcomparebool) ): # only search for matches if not beeing told to not do to so nearestneighbors = idx.nearestNeighbor( feat.geometry(), neighbors=maxneighbors, maxDistance=maxdistance ) # get the featureids of the maximum specified number of near neighbors within a maximum distance try: nearestneighbors.remove( feat.id() ) # remove the current feature from this list (otherwise the nearest feature by == operator would always be itself...) except: pass # ignore on error for near in nearestneighbors: # for each feature iterate over the nearest ones (the index is already sorted by distance, so the first match will be the nearest match) if op_func( layer.getFeature(near)[attrfield], feat[attrfield] ): # if the current nearest attribute is (chosen operator here) than the current feature ones, then new_feat['near_id'] = layer.getFeature( near )[idfield] # get the near matchs's id value and fill the current feature with its value new_feat['near_attr'] = layer.getFeature( near )[attrfield] # also get the attribute value of this near feature new_feat['near_dist'] = feat.geometry().distance( layer.getFeature(near).geometry() ) # and finally calculate the distance between the current feature and the nearest matching feature break # break the for loop of near features and continue with the next feat else: # do not search for near neighbor matches if given value is (operator here) than x pass # do nothing and continue adding the feature sink.addFeature( new_feat, QgsFeatureSink.FastInsert) # add feature to the output feedback.setProgress(int(current * total)) # Set Progress in Progressbar if feedback.isCanceled(): # Cancel algorithm if button is pressed break return {self.OUTPUT: dest_id} # Return result of algorithm
def processAlgorithm(self, parameters, context, feedback): line_source = self.parameterAsSource(parameters, self.LINES, context) if line_source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.LINES)) poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) if poly_source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.POLYGONS)) length_field_name = self.parameterAsString(parameters, self.LEN_FIELD, context) count_field_name = self.parameterAsString(parameters, self.COUNT_FIELD, context) fields = poly_source.fields() if fields.lookupField(length_field_name) < 0: fields.append(QgsField(length_field_name, QVariant.Double)) length_field_index = fields.lookupField(length_field_name) if fields.lookupField(count_field_name) < 0: fields.append(QgsField(count_field_name, QVariant.Int)) count_field_index = fields.lookupField(count_field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) spatialIndex = QgsSpatialIndex(line_source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) distArea = QgsDistanceArea() distArea.setSourceCrs(poly_source.sourceCrs(), context.transformContext()) distArea.setEllipsoid(context.project().ellipsoid()) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0 for current, poly_feature in enumerate(features): if feedback.isCanceled(): break output_feature = QgsFeature() count = 0 length = 0 if poly_feature.hasGeometry(): poly_geom = poly_feature.geometry() has_intersections = False lines = spatialIndex.intersects(poly_geom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(poly_geom.constGet()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids(lines).setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext()) for line_feature in line_source.getFeatures(request): if feedback.isCanceled(): break if engine.intersects(line_feature.geometry().constGet()): outGeom = poly_geom.intersection(line_feature.geometry()) length += distArea.measureLength(outGeom) count += 1 output_feature.setGeometry(poly_geom) attrs = poly_feature.attributes() if length_field_index == len(attrs): attrs.append(length) else: attrs[length_field_index] = length if count_field_index == len(attrs): attrs.append(count) else: attrs[count_field_index] = count output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def enclaveRemover(self): field_id = self.activeLayer.fieldNameIndex(self.distfield) self.activeLayer.startEditing() # Create a dictionary of all features feature_dict = {f.id(): f for f in self.activeLayer.getFeatures()} QgsMessageLog.logMessage("Building spatial index...") # Build a spatial index index = QgsSpatialIndex() for f in feature_dict.values(): index.insertFeature(f) QgsMessageLog.logMessage("Finding neighbors...") # Loop through all features and find features that touch each feature for f in feature_dict.values(): geom = f.geometry() # Find all features that intersect the bounding box of the current feature. # We use spatial index to find the features intersecting the bounding box # of the current feature. This will narrow down the features that we need # to check neighboring features. intersecting_ids = index.intersects(geom.boundingBox()) # Initalize neighbors list and sum neighbors = [] neighbors_district = -1 finished = 0 if f[self.distfield] == 0: QgsMessageLog.logMessage("feature " + str(f.id()) + " with null distfield found!") while neighbors_district <> -2 and finished == 0: finished = 0 for intersecting_id in intersecting_ids: # Look up the feature from the dictionary intersecting_f = feature_dict[intersecting_id] QgsMessageLog.logMessage("Neighbor found!") # For our purpose we consider a feature as 'neighbor' if it touches or # intersects a feature. We use the 'disjoint' predicate to satisfy # these conditions. So if a feature is not disjoint, it is a neighbor. if (f != intersecting_f and not intersecting_f.geometry().disjoint(geom)): if intersecting_f[self.distfield] > 0: QgsMessageLog.logMessage( "Neighbor found with > 0!") if neighbors_district == -1: neighbors_district = intersecting_f[ self.distfield] QgsMessageLog.logMessage( "neighbors_district set to " + str(neighbors_district)) elif neighbors_district != intersecting_f[ self.distfield]: neighbors_district = -2 QgsMessageLog.logMessage( "neighbors_district set to " + str(neighbors_district) + ", " + str(intersecting_f[self.distfield]) + " not matching") if neighbors_district > 0: QgsMessageLog.logMessage( str(f.id()) + " updating district to " + str(neighbors_district)) self.activeLayer.changeAttributeValue( f.id(), field_id, neighbors_district) # Update the layer with new attribute values. finished = 1 self.activeLayer.commitChanges()
class PyProvider(QgsVectorDataProvider): next_feature_id = 1 @classmethod def providerKey(cls): """Returns the memory provider key""" return 'pythonprovider' @classmethod def description(cls): """Returns the memory provider description""" return 'Python Test Provider' @classmethod def createProvider(cls, uri, providerOptions): return PyProvider(uri, providerOptions) # Implementation of functions from QgsVectorDataProvider def __init__(self, uri='', providerOptions=QgsDataProvider.ProviderOptions()): super().__init__(uri) # Use the memory layer to parse the uri mlayer = QgsVectorLayer(uri, 'ml', 'memory') self.setNativeTypes(mlayer.dataProvider().nativeTypes()) self._uri = uri self._fields = mlayer.fields() self._wkbType = mlayer.wkbType() self._features = {} self._extent = QgsRectangle() self._extent.setMinimal() self._subset_string = '' self._crs = mlayer.crs() self._spatialindex = None self._provider_options = providerOptions if 'index=yes' in self._uri: self.createSpatialIndex() def featureSource(self): return PyFeatureSource(self) def dataSourceUri(self, expandAuthConfig=True): return self._uri def storageType(self): return "Python test memory storage" def getFeatures(self, request=QgsFeatureRequest()): return QgsFeatureIterator( PyFeatureIterator(PyFeatureSource(self), request)) def uniqueValues(self, fieldIndex, limit=1): results = set() if fieldIndex >= 0 and fieldIndex < self.fields().count(): req = QgsFeatureRequest() req.setFlags(QgsFeatureRequest.NoGeometry) req.setSubsetOfAttributes([fieldIndex]) for f in self.getFeatures(req): results.add(f.attributes()[fieldIndex]) return results def wkbType(self): return self._wkbType def featureCount(self): if not self.subsetString(): return len(self._features) else: req = QgsFeatureRequest() req.setFlags(QgsFeatureRequest.NoGeometry) req.setSubsetOfAttributes([]) return len([f for f in self.getFeatures(req)]) def fields(self): return self._fields def addFeatures(self, flist, flags=None): added = False f_added = [] for f in flist: if f.hasGeometry() and (f.geometry().wkbType() != self.wkbType()): return added, f_added for f in flist: _f = QgsFeature(self.fields()) _f.setGeometry(f.geometry()) attrs = [None for i in range(_f.fields().count())] for i in range(min(len(attrs), len(f.attributes()))): attrs[i] = f.attributes()[i] _f.setAttributes(attrs) _f.setId(self.next_feature_id) self._features[self.next_feature_id] = _f self.next_feature_id += 1 added = True f_added.append(_f) if self._spatialindex is not None: self._spatialindex.insertFeature(_f) if len(f_added): self.clearMinMaxCache() self.updateExtents() return added, f_added def deleteFeatures(self, ids): if not ids: return True removed = False for id in ids: if id in self._features: if self._spatialindex is not None: self._spatialindex.deleteFeature(self._features[id]) del self._features[id] removed = True if removed: self.clearMinMaxCache() self.updateExtents() return removed def addAttributes(self, attrs): try: for new_f in attrs: if new_f.type() not in (QVariant.Int, QVariant.Double, QVariant.String, QVariant.Date, QVariant.Time, QVariant.DateTime, QVariant.LongLong, QVariant.StringList, QVariant.List): continue self._fields.append(new_f) for f in self._features.values(): old_attrs = f.attributes() old_attrs.append(None) f.setAttributes(old_attrs) self.clearMinMaxCache() return True except Exception: return False def renameAttributes(self, renamedAttributes): result = True # We need to replace all fields because python bindings return a copy from [] and at() new_fields = [self._fields.at(i) for i in range(self._fields.count())] for fieldIndex, new_name in renamedAttributes.items(): if fieldIndex < 0 or fieldIndex >= self._fields.count(): result = False continue if self._fields.indexFromName(new_name) >= 0: #field name already in use result = False continue new_fields[fieldIndex].setName(new_name) if result: self._fields = QgsFields() for i in range(len(new_fields)): self._fields.append(new_fields[i]) return result def deleteAttributes(self, attributes): attrIdx = sorted(attributes, reverse=True) # delete attributes one-by-one with decreasing index for idx in attrIdx: self._fields.remove(idx) for f in self._features.values(): attr = f.attributes() del (attr[idx]) f.setAttributes(attr) self.clearMinMaxCache() return True def changeAttributeValues(self, attr_map): for feature_id, attrs in attr_map.items(): try: f = self._features[feature_id] except KeyError: continue for k, v in attrs.items(): f.setAttribute(k, v) self.clearMinMaxCache() return True def changeGeometryValues(self, geometry_map): for feature_id, geometry in geometry_map.items(): try: f = self._features[feature_id] f.setGeometry(geometry) except KeyError: continue self.updateExtents() return True def allFeatureIds(self): return list(self._features.keys()) def subsetString(self): return self._subset_string def setSubsetString(self, subsetString): if subsetString == self._subset_string: return True self._subset_string = subsetString self.updateExtents() self.clearMinMaxCache() self.dataChanged.emit() return True def supportsSubsetString(self): return True def createSpatialIndex(self): if self._spatialindex is None: self._spatialindex = QgsSpatialIndex() for f in self._features.values(): self._spatialindex.insertFeature(f) return True def capabilities(self): return QgsVectorDataProvider.AddFeatures | QgsVectorDataProvider.DeleteFeatures | QgsVectorDataProvider.CreateSpatialIndex | QgsVectorDataProvider.ChangeGeometries | QgsVectorDataProvider.ChangeAttributeValues | QgsVectorDataProvider.AddAttributes | QgsVectorDataProvider.DeleteAttributes | QgsVectorDataProvider.RenameAttributes | QgsVectorDataProvider.SelectAtId | QgsVectorDataProvider.CircularGeometries #/* Implementation of functions from QgsDataProvider */ def name(self): return self.providerKey() def extent(self): if self._extent.isEmpty() and self._features: self._extent.setMinimal() if not self._subset_string: # fast way - iterate through all features for feat in self._features.values(): if feat.hasGeometry(): self._extent.combineExtentWith( feat.geometry().boundingBox()) else: for f in self.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([])): if f.hasGeometry(): self._extent.combineExtentWith( f.geometry().boundingBox()) elif not self._features: self._extent.setMinimal() return QgsRectangle(self._extent) def updateExtents(self): self._extent.setMinimal() def isValid(self): return True def crs(self): return self._crs
def regularMatrix(self, parameters, context, source, inField, target_source, targetField, nPoints, feedback): distArea = QgsDistanceArea() distArea.setSourceCrs(source.sourceCrs(), context.transformContext()) distArea.setEllipsoid(context.project().ellipsoid()) inIdx = source.fields().lookupField(inField) targetIdx = target_source.fields().lookupField(targetField) index = QgsSpatialIndex( target_source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(source.sourceCrs(), context.transformContext())), feedback) first = True sink = None dest_id = None features = source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([inIdx])) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break inGeom = inFeat.geometry() if first: featList = index.nearestNeighbor(inGeom.asPoint(), nPoints) first = False fields = QgsFields() input_id_field = source.fields()[inIdx] input_id_field.setName('ID') fields.append(input_id_field) for f in target_source.getFeatures( QgsFeatureRequest().setFilterFids( featList).setSubsetOfAttributes([ targetIdx ]).setDestinationCrs(source.sourceCrs(), context.transformContext())): fields.append( QgsField(str(f[targetField]), QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) data = [inFeat[inField]] for target in target_source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( []).setFilterFids(featList).setDestinationCrs( source.sourceCrs(), context.transformContext())): if feedback.isCanceled(): break outGeom = target.geometry() dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint()) data.append(dist) out_feature = QgsFeature() out_feature.setGeometry(inGeom) out_feature.setAttributes(data) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) bbox = source.sourceExtent() sourceIndex = QgsSpatialIndex(source, feedback) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(p, index, minDistance, points): request = QgsFeatureRequest().setFilterFids(ids).setSubsetOfAttributes([]) for f in source.getFeatures(request): if feedback.isCanceled(): break tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo(self.tr('Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) return {self.OUTPUT: dest_id}
class autoRedistrict: """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', 'autoRedistrict_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Automated Redistricting') # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads self.first_start = None #initialise the other variables self.distfield = None self.popfield = None self.geofield = None self.activeLayer = None self.sortType = 0 self.totalpop = 0 self.targetpop = 0 self.districts = 0 self.sortIndex = 0 self.spatialIndex = QgsSpatialIndex() self.distpop = [] self.feature_dict = {} # 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('autoRedistrict', 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: # Adds plugin icon to Plugins toolbar self.iface.addToolBarIcon(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/autoRedistrict/icon.png' self.add_action(icon_path, text=self.tr(u'Auto Redistricting'), callback=self.run, parent=self.iface.mainWindow()) # will be set False in run() self.first_start = True def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr(u'&Automated Redistricting'), action) self.iface.removeToolBarIcon(action) def run(self): """Run method that performs all the real work""" #create the triggers - only need one # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = autoRedistrictDialog() self.dlg.cmbActiveLayer.currentIndexChanged.connect( self.updateFieldCombos) self.dlg.cmbDirection.clear() self.dlg.cmbDirection.addItems([ 'West to East', 'East to West', 'North to South', 'South to North' ]) self.updateDialog() # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: self.createVariables() self.initialiseSpatialIndex() # self.resetDistrictColumn() self.redistrictLayer() def updateDialog(self): layers = [ tree_layer.layer() for tree_layer in QgsProject.instance().layerTreeRoot().findLayers() ] layer_list = [] for layer in layers: layer_list.append(layer.name()) self.dlg.cmbActiveLayer.clear() self.dlg.cmbActiveLayer.addItems(layer_list) def updateFieldCombos(self): self.dlg.cmbPopField.clear() self.dlg.cmbDistField.clear() self.dlg.cmbGeoField.clear() # activeDirectionIndex = self.dlg.cmbDirection.currentIndex() layers = [ tree_layer.layer() for tree_layer in QgsProject.instance().layerTreeRoot().findLayers() ] selectedLayerIndex = self.dlg.cmbActiveLayer.currentIndex() selectedLayer = layers[selectedLayerIndex] self.dlg.cmbGeoField.addItems(['None']) if hasattr(selectedLayer, 'fields'): fields = selectedLayer.fields() field_names = [field.name() for field in fields] self.dlg.cmbPopField.addItems(field_names) self.dlg.cmbDistField.addItems(field_names) self.dlg.cmbGeoField.addItems(field_names) def createVariables(self): self.iface.statusBarIface().showMessage(u"Creating variables...") QCoreApplication.processEvents() self.popfield = self.dlg.cmbPopField.currentText() self.distfield = self.dlg.cmbDistField.currentText() self.geofield = self.dlg.cmbGeoField.currentText() self.sortIndex = self.dlg.cmbDirection.currentIndex() layers = [ tree_layer.layer() for tree_layer in QgsProject.instance().layerTreeRoot().findLayers() ] self.districts = self.dlg.inpDistricts.value() del self.distpop self.distpop = [] self.distpop.append(0) selectedLayerIndex = self.dlg.cmbActiveLayer.currentIndex() selectedLayer = layers[selectedLayerIndex] self.activeLayer = selectedLayer for feature in self.activeLayer.getFeatures(): self.totalpop = self.totalpop + int(feature[self.popfield]) if self.districts > 0: self.targetpop = self.totalpop / self.districts print('districts:' + str(self.districts)) print('targetpop:' + str(self.targetpop)) else: pass def redistrictLayer(self): field_id = self.activeLayer.fields().indexFromName(self.distfield) self.iface.statusBarIface().showMessage(u"Redistricting layer...") QCoreApplication.processEvents() districtctr = 0 found = 0 while found != -1: strExpr = "\"" + self.distfield + "\" = '0' or \"" + self.distfield + "\" = None" expr = QgsExpression(strExpr) if self.sortIndex == 1: # east to west features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_x, reverse=True) elif self.sortIndex == 2: features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_y, reverse=False) elif self.sortIndex == 3: features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_y, reverse=True) else: features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_x) found = 0 for f in features: found = 1 if f[self.distfield] == 0 or f[self.distfield] == None or f[ self.distfield] == 'NULL': print('New District:' + str(f['BLOCKID10']) + '|' + str(f[self.distfield])) districtctr = districtctr + 1 """ if districtctr == 2: strExpr = "\"" + self.distfield + "\" = '1'" expr = QgsExpression(strExpr) iterator = self.activeLayer.getFeatures(QgsFeatureRequest(expr)) ids = [i.id() for i in iterator] self.activeLayer.select(ids) """ print('New district being created:' + str(districtctr)) self.iface.statusBarIface().showMessage( u"Redistricting layer (starting district " + str(districtctr) + ")") QCoreApplication.processEvents() self.distpop.append(0) is_enclave = self.floodFillDistrict(f, districtctr) if is_enclave == 1: districtctr = districtctr - 1 break if found == 0: #close the loop found = -1 self.iface.statusBarIface().showMessage(u"...done.") def initialiseSpatialIndex(self): self.iface.statusBarIface().showMessage( u"Initialising spatial index...") QCoreApplication.processEvents() self.feature_dict = {f.id(): f for f in self.activeLayer.getFeatures()} for f in list(self.feature_dict.values()): self.spatialIndex.addFeature(f) def resetDistrictColumn(self): self.iface.statusBarIface().showMessage(u"Clearing district column:") QCoreApplication.processEvents() field_id = self.activeLayer.fields().indexFromName(self.distfield) self.activeLayer.startEditing() counter = 0 request = QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry).setSubsetOfAttributes( [self.distfield], self.activeLayer.fields()) for f in self.activeLayer.getFeatures(request): counter = counter + 1 self.activeLayer.changeAttributeValue(f.id(), field_id, 0) if counter % 300 == 0: #speeds things up significantly self.activeLayer.commitChanges() self.activeLayer.startEditing() self.iface.statusBarIface().showMessage( u"Clearing district column: " + str(counter) + " records processed") QCoreApplication.processEvents() self.activeLayer.commitChanges() def floodFillDistrict(self, feature, district_number): QgsMessageLog.logMessage("Starting district " + str(district_number)) print('district_number:' + str(district_number)) field_id = self.activeLayer.fields().indexFromName(self.distfield) select_list = [] outside_geo_list = [] neighboring_districts = [] if self.geofield != 'None': activeGeoField = feature[self.geofield] QgsMessageLog.logMessage("GeoField set to " + activeGeoField) geoCounter = 0 self.distpop[district_number] = feature[self.popfield] self.activeLayer.startEditing() feature[self.distfield] = district_number self.activeLayer.changeAttributeValue(feature.id(), field_id, district_number, 0) self.activeLayer.commitChanges() self.activeLayer.startEditing() print('starting at ' + str(feature.id()) + '|' + str(feature[self.distfield]), end=' ') select_list.append(feature) geoCounter = geoCounter + 1 QCoreApplication.processEvents() surrounded = 0 #whether the feature is completely surrounded by another district # Loop through all features and find features that touch each feature counter = 0 for f in select_list: geom = f.geometry() # Find all features that intersect the bounding box of the current feature. # We use spatial index to find the features intersecting the bounding box # of the current feature. This will narrow down the features that we need # to check neighboring features. intersecting_ids = self.spatialIndex.intersects(geom.boundingBox()) counter = counter + 1 if counter % 500 == 0: self.iface.statusBarIface().showMessage( u"Redistricting layer (district " + str(district_number) + ") " + str(counter) + " polygons added, current population " + str(self.distpop[district_number]) + "/" + str(self.targetpop)) QCoreApplication.processEvents() self.activeLayer.commitChanges() self.activeLayer.startEditing() for intersecting_id in intersecting_ids: # Look up the feature from the dictionary intersecting_f = self.feature_dict[intersecting_id] # For our purpose we consider a feature as 'neighbor' if it touches or # intersects a feature. We use the 'disjoint' predicate to satisfy # these conditions. So if a feature is not disjoint, it is a neighbor. # if (f.id() != intersecting_f.id() and feature.id() != intersecting_f.id() and not intersecting_f.geometry().disjoint(geom)): if (f.id() != intersecting_f.id() and feature.id() != intersecting_f.id() and intersecting_f.geometry().intersects(geom)): if intersecting_f[self.distfield] == 0 or intersecting_f[ self.distfield] == None: if intersecting_f[ self. geofield] == activeGeoField or self.geofield == 'None': if intersecting_f not in select_list: if surrounded != -1: surrounded = -1 self.distpop[district_number] = self.distpop[ district_number] + intersecting_f[ self.popfield] intersecting_f[ self.distfield] = district_number self.activeLayer.changeAttributeValue( intersecting_f.id(), field_id, district_number, 0) # print(':' + str(intersecting_f[self.distfield]), end=":") select_list.append(intersecting_f) geoCounter = geoCounter + 1 if self.distpop[ district_number] > self.targetpop: self.activeLayer.commitChanges() return 0 else: if intersecting_f not in outside_geo_list and intersecting_f not in select_list: outside_geo_list.append(intersecting_f) surrounded = -1 elif surrounded != -1: #kill one-district, completely surrounded enclaves as we find them if surrounded == 0: surrounded = intersecting_f[self.distfield] elif surrounded != intersecting_f[self.distfield]: if surrounded > 0: neighboring_districts.append(surrounded) surrounded = -1 elif surrounded == -1: if intersecting_f[ self.distfield] not in neighboring_districts: neighboring_districts.append( intersecting_f[self.distfield]) """ #old code commented out which gets greedy and expands districts, but it doesn't work well - shoots down interstates, etc if self.distpop[intersecting_f[self.distfield]] - intersecting_f[self.popfield] > self.targetpop: self.distpop[intersecting_f[self.distfield]] = self.distpop[intersecting_f[self.distfield]] - intersecting_f[self.popfield] self.distpop[district_number] = self.distpop[district_number] + intersecting_f[self.popfield] intersecting_f[self.distfield] = district_number self.activeLayer.changeAttributeValue(intersecting_f.id(),field_id,district_number,0) select_list.append(intersecting_f) """ if self.distpop[district_number] > self.targetpop: self.activeLayer.commitChanges() return 0 if surrounded > 0: QgsMessageLog.logMessage( "enclave found, reassigning to district " + str(surrounded)) self.activeLayer.changeAttributeValue(feature.id(), field_id, surrounded, 0) self.distpop[district_number] = self.distpop[ district_number] - feature[self.popfield] self.distpop[surrounded] = self.distpop[surrounded] + feature[ self.popfield] self.activeLayer.commitChanges() return 1 #if geoCounter is zero, this means we change the active geography geoCounter = geoCounter - 1 activeGeoField = '' if geoCounter == 0 and self.geofield != 'None': for of in outside_geo_list: if activeGeoField == '': activeGeoField = of[self.geofield] QgsMessageLog.logMessage("GeoField updated to " + activeGeoField) if of[self.geofield] == activeGeoField: select_list.append(of) outside_geo_list.remove(of) self.distpop[district_number] = self.distpop[ district_number] + of[self.popfield] of[self.distfield] = district_number self.activeLayer.changeAttributeValue( of.id(), field_id, district_number, 0) geoCounter = geoCounter + 1 if self.distpop[district_number] < (self.targetpop * (1 - 0.9)): new_district = 0 lower_pop = -1 for n in neighboring_districts: if self.distpop[n] > lower_pop or lower_pop == -1: lower_pop = self.distpop[n] new_district = n QgsMessageLog.logMessage( "target population too low, reassigning to district " + str(new_district)) for f in select_list: self.activeLayer.changeAttributeValue(f.id(), field_id, new_district, 0) self.distpop[district_number] = self.distpop[ district_number] - feature[self.popfield] self.distpop[new_district] = self.distpop[ new_district] + feature[self.popfield] self.activeLayer.commitChanges() return 1
def testGetGeometry(self): idx = QgsSpatialIndex() idx2 = QgsSpatialIndex(QgsSpatialIndex.FlagStoreFeatureGeometries) fid = 0 for y in range(5): for x in range(10, 15): ft = QgsFeature() ft.setId(fid) ft.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y))) idx.insertFeature(ft) idx2.insertFeature(ft) fid += 1 # not storing geometries, a keyerror should be raised with self.assertRaises(KeyError): idx.geometry(-100) with self.assertRaises(KeyError): idx.geometry(1) with self.assertRaises(KeyError): idx.geometry(2) with self.assertRaises(KeyError): idx.geometry(1000) self.assertEqual(idx2.geometry(1).asWkt(1), 'Point (11 0)') self.assertEqual(idx2.geometry(2).asWkt(1), 'Point (12 0)') with self.assertRaises(KeyError): idx2.geometry(-100) with self.assertRaises(KeyError): idx2.geometry(1000)
def linearMatrix(self, parameters, context, source, inField, target_source, targetField, matType, nPoints, feedback): inIdx = source.fields().lookupField(inField) outIdx = target_source.fields().lookupField(targetField) fields = QgsFields() input_id_field = source.fields()[inIdx] input_id_field.setName('InputID') fields.append(input_id_field) if matType == 0: target_id_field = target_source.fields()[outIdx] target_id_field.setName('TargetID') fields.append(target_id_field) fields.append(QgsField('Distance', QVariant.Double)) else: fields.append(QgsField('MEAN', QVariant.Double)) fields.append(QgsField('STDDEV', QVariant.Double)) fields.append(QgsField('MIN', QVariant.Double)) fields.append(QgsField('MAX', QVariant.Double)) out_wkb = QgsWkbTypes.multiType(source.wkbType()) if matType == 0 else source.wkbType() (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, out_wkb, source.sourceCrs()) index = QgsSpatialIndex(target_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(source.sourceCrs(), context.transformContext())), feedback) distArea = QgsDistanceArea() distArea.setSourceCrs(source.sourceCrs(), context.transformContext()) distArea.setEllipsoid(context.project().ellipsoid()) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([inIdx])) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break inGeom = inFeat.geometry() inID = str(inFeat.attributes()[inIdx]) featList = index.nearestNeighbor(inGeom.asPoint(), nPoints) distList = [] vari = 0.0 request = QgsFeatureRequest().setFilterFids(featList).setSubsetOfAttributes([outIdx]).setDestinationCrs(source.sourceCrs(), context.transformContext()) for outFeat in target_source.getFeatures(request): if feedback.isCanceled(): break outID = outFeat.attributes()[outIdx] outGeom = outFeat.geometry() dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint()) if matType == 0: out_feature = QgsFeature() out_geom = QgsGeometry.unaryUnion([inFeat.geometry(), outFeat.geometry()]) out_feature.setGeometry(out_geom) out_feature.setAttributes([inID, outID, dist]) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) else: distList.append(float(dist)) if matType != 0: mean = sum(distList) / len(distList) for i in distList: vari += (i - mean) * (i - mean) vari = math.sqrt(vari / len(distList)) out_feature = QgsFeature() out_feature.setGeometry(inFeat.geometry()) out_feature.setAttributes([inID, mean, vari, min(distList), max(distList)]) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) spatialIndex = QgsSpatialIndex(source, feedback) distance = QgsDistanceArea() distance.setSourceCrs(source.sourceCrs(), context.transformContext()) distance.setEllipsoid(context.project().ellipsoid()) sumDist = 0.00 A = source.sourceExtent() A = float(A.width() * A.height()) features = source.getFeatures() count = source.featureCount() total = 100.0 / count if count else 1 for current, feat in enumerate(features): if feedback.isCanceled(): break neighbourID = spatialIndex.nearestNeighbor( feat.geometry().asPoint(), 2)[1] request = QgsFeatureRequest().setFilterFid( neighbourID).setSubsetOfAttributes([]) neighbour = next(source.getFeatures(request)) sumDist += distance.measureLine(neighbour.geometry().asPoint(), feat.geometry().asPoint()) feedback.setProgress(int(current * total)) do = float(sumDist) / count de = float(0.5 / math.sqrt(count / A)) d = float(do / de) SE = float(0.26136 / math.sqrt(count**2 / A)) zscore = float((do - de) / SE) results = {} results[self.OBSERVED_MD] = do results[self.EXPECTED_MD] = de results[self.NN_INDEX] = d results[self.POINT_COUNT] = count results[self.Z_SCORE] = zscore if output_file: data = [] data.append('Observed mean distance: ' + str(do)) data.append('Expected mean distance: ' + str(de)) data.append('Nearest neighbour index: ' + str(d)) data.append('Number of points: ' + str(count)) data.append('Z-Score: ' + str(zscore)) self.createHTML(output_file, data) results[self.OUTPUT_HTML_FILE] = output_file return results
def processAlgorithm(self, feedback): 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, QgsWkbTypes.multiType(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)) for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: 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 inGeom.isNull(): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList is 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 outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) writer.addFeature(outFeat) feedback.setProgress(int(current * total)) del writer
def createSpatialIndex(self): if self._spatialindex is None: self._spatialindex = QgsSpatialIndex() for f in self._features.values(): self._spatialindex.insertFeature(f) return True
def processAlgorithm(self, parameters, context, feedback): layer = self.parameterAsVectorLayer(parameters, self.Centerline, context) layer2 = self.parameterAsVectorLayer(parameters, self.Direction, context) context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) index = QgsSpatialIndex(layer2.getFeatures()) fet = QgsFeature() fields = QgsFields() fields.append(QgsField("ID", QVariant.Int)) field_names = ['Distance', 'RDistance', 'SP_Dist', 'SP_RDist'] for name in field_names: fields.append(QgsField(name, QVariant.Double)) AD = False if layer2.fields().indexFromName('Distance') != -1: AD = True fields.append(QgsField('AlongDist', QVariant.Double)) (writer, dest_id) = self.parameterAsSink(parameters, self.Output, context, fields, QgsWkbTypes.LineString, layer.sourceCrs()) Precision = 5 SPS = {} SPE = {} values = {} values2 = {} data = {feature.id(): feature for feature in layer2.getFeatures()} total = 0 feedback.pushInfo( QCoreApplication.translate('Update', 'Defining Centerline Paths')) for enum, feature in enumerate(layer.getFeatures()): total += 1 try: pnt = feature.geometry() if pnt.isMultipart(): pnt = pnt.asMultiPolyline()[0] else: pnt = pnt.asPolyline() startx, starty = round(pnt[0][0], Precision), round(pnt[0][1], Precision) endx, endy = round(pnt[-1][0], Precision), round(pnt[-1][1], Precision) ID = feature['ID'] c = feature['Distance'] if ID in SPS: #Get start and endpoint of each centerline v = values[ID] v2 = values2[ID] if c > v: SPS[ID] = [(startx, starty), (endx, endy)] values[ID] = c if c < v2: SPE[ID] = [(startx, starty), (endx, endy)] values2[ID] = c else: SPS[ID] = [(startx, starty), (endx, endy)] values[ID] = c SPE[ID] = [(startx, starty), (endx, endy)] values2[ID] = c except Exception as e: #feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) continue ##Possible Collapsed Polyline? del values, values2 feedback.pushInfo( QCoreApplication.translate('Update', 'Correcting Centerline Direction')) total = 100.0 / float(total) ID = None for enum, feature in enumerate(layer.getFeatures()): if total != -1: feedback.setProgress(int(enum * total)) try: try: geom = feature.geometry().asPolyline() except Exception: geom = feature.geometry().asMultiPolyline()[0] start, end = geom[0], geom[-1] startx, starty = start endx, endy = end curID = feature['ID'] if curID != ID: ID = curID reverse = False SP = SPS[curID] EP = SPE[curID] startx, starty = round(SP[0][0], Precision), round( SP[0][1], Precision) v = index.nearestNeighbor(QgsPointXY(startx, starty), 1) midx, midy = data[v[0]].geometry().centroid().asPoint() dx, dy = startx - midx, starty - midy if AD: shortestPath = data[v[0]]['Distance'] + sqrt((dx**2) + (dy**2)) startx, starty = round(SP[1][0], Precision), round( SP[1][1], Precision) v = index.nearestNeighbor(QgsPointXY(startx, starty), 1) dx, dy = startx - midx, starty - midy SPd = data[v[0]]['Distance'] + sqrt((dx**2) + (dy**2)) if SPd < shortestPath: shortestPath = SPd else: shortestPath = sqrt((dx**2) + (dy**2)) startx, starty = round(EP[0][0], Precision), round( EP[0][1], Precision) v = index.nearestNeighbor(QgsPointXY(startx, starty), 1) midx, midy = data[v[0]].geometry().centroid().asPoint() dx, dy = startx - midx, starty - midy if AD: shortestPath2 = data[v[0]]['Distance'] + sqrt((dx**2) + (dy**2)) startx, starty = round(SP[1][0], Precision), round( SP[1][1], Precision) v = index.nearestNeighbor(QgsPointXY(startx, starty), 1) dx, dy = startx - midx, starty - midy SPd = data[v[0]]['Distance'] + sqrt((dx**2) + (dy**2)) if SPd < shortestPath2: shortestPath2 = SPd else: shortestPath2 = sqrt((dx**2) + (dy**2)) if shortestPath2 > shortestPath: reverse = True dist = shortestPath else: dist = shortestPath2 D = feature['Distance'] D2 = feature['RDistance'] SP = feature['SP_Dist'] SP2 = feature['SP_RDist'] if reverse: rows = [curID, D2, D, SP2, SP] else: rows = [curID, D, D2, SP, SP2] D2 = D if AD: rows.append(float(dist) + D2) fet.setGeometry(feature.geometry()) fet.setAttributes(rows) writer.addFeature(fet) except Exception as e: feedback.pushInfo( QCoreApplication.translate('Update', '%s' % (e))) continue return {self.Output: dest_id}
def processAlgorithm(self, feedback): layer = dataobjects.getObjectFromUri( self.getParameterValue(self.VECTOR)) pointCount = float(self.getParameterValue(self.POINT_NUMBER)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QgsWkbTypes.Point, layer.crs()) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 featureCount = layer.featureCount() total = 100.0 / pointCount index = QgsSpatialIndex() points = dict() da = QgsDistanceArea() request = QgsFeatureRequest() random.seed() while nIterations < maxIterations and nPoints < pointCount: # pick random feature fid = random.randint(0, featureCount - 1) f = next( layer.getFeatures( request.setFilterFid(fid).setSubsetOfAttributes([]))) fGeom = f.geometry() if fGeom.isMultipart(): lines = fGeom.asMultiPolyline() # pick random line lineId = random.randint(0, len(lines) - 1) vertices = lines[lineId] else: vertices = fGeom.asPolyline() # pick random segment if len(vertices) == 2: vid = 0 else: vid = random.randint(0, len(vertices) - 2) startPoint = vertices[vid] endPoint = vertices[vid + 1] length = da.measureLine(startPoint, endPoint) dist = length * random.random() if dist > minDistance: d = dist / (length - dist) rx = (startPoint.x() + d * endPoint.x()) / (1 + d) ry = (startPoint.y() + d * endPoint.y()) / (1 + d) # generate random point pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) if vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog( ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) del writer
class Serval(object): LINE_SELECTION = "line" POLYGON_SELECTION = "polygon" RGB = "RGB" SINGLE_BAND = "Single band" def __init__(self, iface): self.iface = iface self.canvas = self.iface.mapCanvas() self.plugin_dir = os.path.dirname(__file__) self.uc = UserCommunication(iface, 'Serval') self.load_settings() self.raster = None self.handler = None self.spin_boxes = None self.exp_dlg = None self.exp_builder = None self.block_pts_layer = None self.px, self.py = [0, 0] self.last_point = QgsPointXY(0, 0) self.rbounds = None self.changes = dict() # dict with rasters changes {raster_id: RasterChanges instance} self.project = QgsProject.instance() self.crs_transform = None self.all_touched = None self.selection_mode = None self.spatial_index_time = dict() # {layer_id: creation time} self.spatial_index = dict() # {layer_id: spatial index} self.selection_layers_count = 1 self.debug = DEBUG self.logger = get_logger() if self.debug else None self.menu = u'Serval' self.actions = [] self.actions_always_on = [] self.toolbar = self.iface.addToolBar(u'Serval Main Toolbar') self.toolbar.setObjectName(u'Serval Main Toolbar') self.toolbar.setToolTip(u'Serval Main Toolbar') self.sel_toolbar = self.iface.addToolBar(u'Serval Selection Toolbar') self.sel_toolbar.setObjectName(u'Serval Selection Toolbar') self.sel_toolbar.setToolTip(u'Serval Selection Toolbar') # Map tools self.probe_tool = QgsMapToolEmitPoint(self.canvas) self.probe_tool.setObjectName('ServalProbeTool') self.probe_tool.setCursor(QCursor(QPixmap(icon_path('probe_tool.svg')), hotX=2, hotY=22)) self.probe_tool.canvasClicked.connect(self.point_clicked) self.draw_tool = QgsMapToolEmitPoint(self.canvas) self.draw_tool.setObjectName('ServalDrawTool') self.draw_tool.setCursor(QCursor(QPixmap(icon_path('draw_tool.svg')), hotX=2, hotY=22)) self.draw_tool.canvasClicked.connect(self.point_clicked) self.selection_tool = RasterCellSelectionMapTool(self.iface, self.uc, self.raster, debug=self.debug) self.selection_tool.setObjectName('RasterSelectionTool') self.map_tool_btn = dict() # {map tool: button activating the tool} self.iface.currentLayerChanged.connect(self.set_active_raster) self.project.layersAdded.connect(self.set_active_raster) self.canvas.mapToolSet.connect(self.check_active_tool) self.register_exp_functions() def load_settings(self): """Return plugin settings dict - default values are overriden by user prefered values from QSettings.""" self.default_settings = { "undo_steps": {"value": 3, "vtype": int}, } self.settings = dict() s = QSettings() s.beginGroup("serval") for k, v in self.default_settings.items(): user_val = s.value(k, v["value"], v["vtype"]) self.settings[k] = user_val def edit_settings(self): """Open dialog with plugin settings.""" s = QSettings() s.beginGroup("serval") k = "undo_steps" cur_val = self.settings[k] val_type = self.default_settings[k]["vtype"] cur_steps = s.value(k, cur_val, val_type) label = 'Nr of Undo/Redo steps:' steps, ok = QInputDialog.getInt(None, "Serval Settings", label, cur_steps) if not ok: return if steps >= 0: s.setValue("undo_steps", steps) self.load_settings() self.uc.show_info("Some new settings may require QGIS restart.") def initGui(self): _ = self.add_action( 'serval_icon.svg', text=u'Show Serval Toolbars', add_to_menu=True, callback=self.show_toolbar, always_on=True, ) _ = self.add_action( 'serval_icon.svg', text=u'Hide Serval Toolbars', add_to_menu=True, callback=self.hide_toolbar, always_on=True, ) self.probe_btn = self.add_action( 'probe.svg', text="Probe raster", callback=self.activate_probing, add_to_toolbar=self.toolbar, checkable=True, ) self.map_tool_btn[self.probe_tool] = self.probe_btn self.color_btn = QgsColorButton() self.color_btn.setColor(Qt.gray) self.color_btn.setMinimumSize(QSize(40, 24)) self.color_btn.setMaximumSize(QSize(40, 24)) self.toolbar.addWidget(self.color_btn) self.color_picker_connection(connect=True) self.color_btn.setDisabled(True) self.toolbar.addWidget(QLabel("Band:")) self.bands_cbo = QComboBox() self.bands_cbo.addItem("1", 1) self.toolbar.addWidget(self.bands_cbo) self.bands_cbo.currentIndexChanged.connect(self.update_active_bands) self.bands_cbo.setDisabled(True) self.spin_boxes = BandBoxes() self.toolbar.addWidget(self.spin_boxes) self.spin_boxes.enter_hit.connect(self.apply_spin_box_values) self.draw_btn = self.add_action( 'draw.svg', text="Apply Value(s) To Single Cell", callback=self.activate_drawing, add_to_toolbar=self.toolbar, checkable=True, ) self.map_tool_btn[self.draw_tool] = self.draw_btn self.apply_spin_box_values_btn = self.add_action( 'apply_const_value.svg', text="Apply Value(s) to Selection", callback=self.apply_spin_box_values, add_to_toolbar=self.toolbar, ) self.gom_btn = self.add_action( 'apply_nodata_value.svg', text="Apply NoData to Selection", callback=self.apply_nodata_value, add_to_toolbar=self.toolbar, ) self.exp_dlg_btn = self.add_action( 'apply_expression_value.svg', text="Apply Expression Value To Selection", callback=self.define_expression, add_to_toolbar=self.toolbar, checkable=False, ) self.low_pass_filter_btn = self.add_action( 'apply_low_pass_filter.svg', text="Apply Low-Pass 3x3 Filter To Selection", callback=self.apply_low_pass_filter, add_to_toolbar=self.toolbar, checkable=False, ) self.undo_btn = self.add_action( 'undo.svg', text="Undo", callback=self.undo, add_to_toolbar=self.toolbar, ) self.redo_btn = self.add_action( 'redo.svg', text="Redo", callback=self.redo, add_to_toolbar=self.toolbar, ) self.set_nodata_btn = self.add_action( 'set_nodata.svg', text="Edit Raster NoData Values", callback=self.set_nodata, add_to_toolbar=self.toolbar, ) self.settings_btn = self.add_action( 'edit_settings.svg', text="Serval Settings", callback=self.edit_settings, add_to_toolbar=self.toolbar, always_on=True, ) self.show_help = self.add_action( 'help.svg', text="Help", add_to_menu=True, callback=self.show_website, add_to_toolbar=self.toolbar, always_on=True, ) # Selection Toolbar line_width_icon = QIcon(icon_path("line_width.svg")) line_width_lab = QLabel() line_width_lab.setPixmap(line_width_icon.pixmap(22, 12)) self.sel_toolbar.addWidget(line_width_lab) self.line_width_sbox = QgsDoubleSpinBox() self.line_width_sbox.setMinimumSize(QSize(50, 24)) self.line_width_sbox.setMaximumSize(QSize(50, 24)) # self.line_width_sbox.setButtonSymbols(QAbstractSpinBox.NoButtons) self.line_width_sbox.setValue(1) self.line_width_sbox.setMinimum(0.01) self.line_width_sbox.setShowClearButton(False) self.line_width_sbox.setToolTip("Selection Line Width") self.line_width_sbox.valueChanged.connect(self.update_selection_tool) self.width_unit_cbo = QComboBox() self.width_units = ("map units", "pixel width", "pixel height", "hairline",) for u in self.width_units: self.width_unit_cbo.addItem(u) self.width_unit_cbo.setToolTip("Selection Line Width Unit") self.sel_toolbar.addWidget(self.line_width_sbox) self.sel_toolbar.addWidget(self.width_unit_cbo) self.width_unit_cbo.currentIndexChanged.connect(self.update_selection_tool) self.line_select_btn = self.add_action( 'select_line.svg', text="Select Raster Cells by Line", callback=self.activate_line_selection, add_to_toolbar=self.sel_toolbar, checkable=True, ) self.polygon_select_btn = self.add_action( 'select_polygon.svg', text="Select Raster Cells by Polygon", callback=self.activate_polygon_selection, add_to_toolbar=self.sel_toolbar, checkable=True, ) self.selection_from_layer_btn = self.add_action( 'select_from_layer.svg', text="Create Selection From Layer", callback=self.selection_from_layer, add_to_toolbar=self.sel_toolbar, ) self.selection_to_layer_btn = self.add_action( 'selection_to_layer.svg', text="Create Memory Layer From Selection", callback=self.selection_to_layer, add_to_toolbar=self.sel_toolbar, ) self.clear_selection_btn = self.add_action( 'clear_selection.svg', text="Clear selection", callback=self.clear_selection, add_to_toolbar=self.sel_toolbar, ) self.toggle_all_touched_btn = self.add_action( 'all_touched.svg', text="Toggle All Touched Get Selected", callback=self.toggle_all_touched, checkable=True, checked=True, add_to_toolbar=self.sel_toolbar, ) self.all_touched = True self.enable_toolbar_actions(enable=False) self.check_undo_redo_btns() def add_action(self, icon_name, callback=None, text="", enabled_flag=True, add_to_menu=False, add_to_toolbar=None, status_tip=None, whats_this=None, checkable=False, checked=False, always_on=False): icon = QIcon(icon_path(icon_name)) action = QAction(icon, text, self.iface.mainWindow()) action.triggered.connect(callback) action.setEnabled(enabled_flag) action.setCheckable(checkable) action.setChecked(checked) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar is not None: add_to_toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) if always_on: self.actions_always_on.append(action) return action def unload(self): self.changes = None if self.selection_tool: self.selection_tool.reset() if self.spin_boxes is not None: self.spin_boxes.remove_spinboxes() for action in self.actions: self.iface.removePluginMenu('Serval', action) self.iface.removeToolBarIcon(action) del self.toolbar del self.sel_toolbar self.iface.actionPan().trigger() self.unregister_exp_functions() def show_toolbar(self): if self.toolbar: self.toolbar.show() self.sel_toolbar.show() def hide_toolbar(self): if self.toolbar: self.toolbar.hide() self.sel_toolbar.hide() @staticmethod def register_exp_functions(): QgsExpression.registerFunction(nearest_feature_attr_value) QgsExpression.registerFunction(nearest_pt_on_line_interpolate_z) QgsExpression.registerFunction(intersecting_features_attr_average) QgsExpression.registerFunction(interpolate_from_mesh) @staticmethod def unregister_exp_functions(): QgsExpression.unregisterFunction('nearest_feature_attr_value') QgsExpression.unregisterFunction('nearest_pt_on_line_interpolate_z') QgsExpression.unregisterFunction('intersecting_features_attr_average') QgsExpression.unregisterFunction('interpolate_from_mesh') def uncheck_all_btns(self): self.probe_btn.setChecked(False) self.draw_btn.setChecked(False) self.gom_btn.setChecked(False) self.line_select_btn.setChecked(False) self.polygon_select_btn.setChecked(False) def check_active_tool(self, cur_tool): self.uncheck_all_btns() if cur_tool in self.map_tool_btn: self.map_tool_btn[cur_tool].setChecked(True) if cur_tool == self.selection_tool: if self.selection_mode == self.LINE_SELECTION: self.line_select_btn.setChecked(True) else: self.polygon_select_btn.setChecked(True) def activate_probing(self): self.mode = 'probe' self.canvas.setMapTool(self.probe_tool) def define_expression(self): if not self.selection_tool.selected_geometries: self.uc.bar_warn("No selection for raster layer. Select some cells and retry...") return self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched) self.handler.create_cell_pts_layer() if self.handler.cell_pts_layer.featureCount() == 0: self.uc.bar_warn("No selection for raster layer. Select some cells and retry...") return self.exp_dlg = QgsExpressionBuilderDialog(self.handler.cell_pts_layer) self.exp_builder = self.exp_dlg.expressionBuilder() self.exp_dlg.accepted.connect(self.apply_exp_value) self.exp_dlg.show() def apply_exp_value(self): if not self.exp_dlg.expressionText() or not self.exp_builder.isExpressionValid(): return QApplication.setOverrideCursor(Qt.WaitCursor) exp = self.exp_dlg.expressionText() idx = self.handler.cell_pts_layer.addExpressionField(exp, QgsField('exp_val', QVariant.Double)) self.handler.exp_field_idx = idx self.handler.write_block() QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def activate_drawing(self): self.mode = 'draw' self.canvas.setMapTool(self.draw_tool) def get_cur_line_width(self): width_coef = { "map units": 1., "pixel width": self.raster.rasterUnitsPerPixelX(), "pixel height": self.raster.rasterUnitsPerPixelY(), "hairline": 0.000001, } return self.line_width_sbox.value() * width_coef[self.width_unit_cbo.currentText()] def set_selection_tool(self, mode): if self.raster is None: self.uc.bar_warn("Select a raster layer") return self.selection_mode = mode self.selection_tool.init_tool(self.raster, mode=self.selection_mode, line_width=self.get_cur_line_width()) self.selection_tool.set_prev_tool(self.canvas.mapTool()) self.canvas.setMapTool(self.selection_tool) def activate_line_selection(self): self.set_selection_tool(self.LINE_SELECTION) def activate_polygon_selection(self): self.set_selection_tool(self.POLYGON_SELECTION) def update_selection_tool(self): """Reactivate the selection tool with updated line width and units.""" if self.selection_mode == self.LINE_SELECTION: self.activate_line_selection() elif self.selection_mode == self.POLYGON_SELECTION: self.activate_polygon_selection() else: pass def apply_values(self, new_values): QApplication.setOverrideCursor(Qt.WaitCursor) self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched) self.handler.write_block(new_values) QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def apply_values_single_cell(self, new_vals): """Create single cell selection and apply the new values.""" cp = self.last_point if self.logger: self.logger.debug(f"Changing single cell for pt {cp}") col, row = self.handler.point_to_index([cp.x(), cp.y()]) px, py = self.handler.index_to_point(row, col, upper_left=False) d = 0.001 bbox = QgsRectangle(px - d, py - d, px + d, py + d) if self.logger: self.logger.debug(f"Changing single cell in {bbox}") QApplication.setOverrideCursor(Qt.WaitCursor) self.handler.select([QgsGeometry.fromRect(bbox)], all_touched_cells=False, transform=False) self.handler.write_block(new_vals) QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def apply_spin_box_values(self): if not self.selection_tool.selected_geometries: return self.apply_values(self.spin_boxes.get_values()) def apply_nodata_value(self): if not self.selection_tool.selected_geometries: return self.apply_values(self.handler.nodata_values) def apply_low_pass_filter(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched) self.handler.write_block(low_pass_filter=True) QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def clear_selection(self): if self.selection_tool: self.selection_tool.clear_all_selections() def selection_from_layer(self): """Create a new selection from layer.""" self.selection_tool.init_tool(self.raster, mode=self.POLYGON_SELECTION, line_width=self.get_cur_line_width()) dlg = LayerSelectDialog() if not dlg.exec_(): return cur_layer = dlg.cbo.currentLayer() if not cur_layer.type() == QgsMapLayerType.VectorLayer: return self.selection_tool.selection_from_layer(cur_layer) def selection_to_layer(self): """Create a memory layer from current selection""" geoms = self.selection_tool.selected_geometries if geoms is None or not self.raster: return crs_str = self.raster.crs().toProj() nr = self.selection_layers_count self.selection_layers_count += 1 mlayer = QgsVectorLayer(f"Polygon?crs={crs_str}&field=fid:int", f"Raster selection {nr}", "memory") fields = mlayer.dataProvider().fields() features = [] for i, geom in enumerate(geoms): feat = QgsFeature(fields) feat["fid"] = i + 1 feat.setGeometry(geom) features.append(feat) mlayer.dataProvider().addFeatures(features) self.project.addMapLayer(mlayer) def toggle_all_touched(self): """Toggle selection mode.""" # button is toggled automatically when clicked, just update the attribute self.all_touched = self.toggle_all_touched_btn.isChecked() def point_clicked(self, point=None, button=None): if self.raster is None: self.uc.bar_warn("Choose a raster to work with...", dur=3) return if self.logger: self.logger.debug(f"Clicked point in canvas CRS: {point if point else self.last_point}") if point is None: ptxy_in_src_crs = self.last_point else: if self.crs_transform: if self.logger: self.logger.debug(f"Transforming clicked point {point}") try: ptxy_in_src_crs = self.crs_transform.transform(point) except QgsCsException as err: self.uc.show_warn( "Point coordinates transformation failed! Check the raster projection:\n\n{}".format(repr(err))) return else: ptxy_in_src_crs = QgsPointXY(point.x(), point.y()) if self.logger: self.logger.debug(f"Clicked point in raster CRS: {ptxy_in_src_crs}") self.last_point = ptxy_in_src_crs ident_vals = self.handler.provider.identify(ptxy_in_src_crs, QgsRaster.IdentifyFormatValue).results() cur_vals = list(ident_vals.values()) # check if the point is within active raster extent if not self.rbounds[0] <= ptxy_in_src_crs.x() <= self.rbounds[2]: self.uc.bar_info("Out of x bounds", dur=3) return if not self.rbounds[1] <= ptxy_in_src_crs.y() <= self.rbounds[3]: self.uc.bar_info("Out of y bounds", dur=3) return if self.mode == 'draw': new_vals = self.spin_boxes.get_values() if self.logger: self.logger.debug(f"Applying const value {new_vals}") self.apply_values_single_cell(new_vals) else: self.spin_boxes.set_values(cur_vals) if 2 < self.handler.bands_nr < 5: self.color_picker_connection(connect=False) self.color_btn.setColor(QColor(*self.spin_boxes.get_values()[:4])) self.color_picker_connection(connect=True) def set_values_from_picker(self, c): """Set bands spinboxes values after color change in the color picker""" values = None if self.handler.bands_nr > 2: values = [c.red(), c.green(), c.blue()] if self.handler.bands_nr == 4: values.append(c.alpha()) if values: self.spin_boxes.set_values(values) def set_nodata(self): """Set NoData value(s) for each band of current raster.""" if not self.raster: self.uc.bar_warn('Select a raster layer to define/change NoData value!') return if self.handler.provider.userNoDataValues(1): note = '\nNote: there is a user defined NODATA value.\nCheck the raster properties (Transparency).' else: note = '' dt = self.handler.provider.dataType(1) # current NODATA value if self.handler.provider.sourceHasNoDataValue(1): cur_nodata = self.handler.provider.sourceNoDataValue(1) if dt < 6: cur_nodata = '{0:d}'.format(int(float(cur_nodata))) else: cur_nodata = '' label = 'Define/change raster NODATA value.\n\n' label += 'Raster src_data type: {}.{}'.format(dtypes[dt]['name'], note) nd, ok = QInputDialog.getText(None, "Define NODATA Value", label, QLineEdit.Normal, str(cur_nodata)) if not ok: return if not is_number(nd): self.uc.show_warn('Wrong NODATA value!') return new_nodata = int(nd) if dt < 6 else float(nd) # set the NODATA value for each band res = [] for nr in self.handler.bands_range: res.append(self.handler.provider.setNoDataValue(nr, new_nodata)) self.handler.provider.sourceHasNoDataValue(nr) if False in res: self.uc.show_warn('Setting new NODATA value failed!') else: self.uc.bar_info('Successful setting new NODATA values!', dur=2) self.set_active_raster() self.raster.triggerRepaint() def check_undo_redo_btns(self): """Enable/Disable undo and redo buttons based on availability of undo/redo for current raster.""" self.undo_btn.setDisabled(True) self.redo_btn.setDisabled(True) if self.raster is None or self.raster.id() not in self.changes: return changes = self.changes[self.raster.id()] if changes.nr_undos() > 0: self.undo_btn.setEnabled(True) if changes.nr_redos() > 0: self.redo_btn.setEnabled(True) def enable_toolbar_actions(self, enable=True): """Enable / disable all toolbar actions but Help (for vectors and unsupported rasters)""" for widget in self.actions + [self.width_unit_cbo, self.line_width_sbox]: widget.setEnabled(enable) if widget in self.actions_always_on: widget.setEnabled(True) self.spin_boxes.enable(enable) @staticmethod def check_layer(layer): """Check if we can work with the raster""" if layer is None: return False if layer.type() != QgsMapLayerType.RasterLayer: return False if layer.providerType() != 'gdal': return False if all([ layer.isValid(), layer.crs() is not None, check_gdal_driver_create_option(layer), # GDAL driver has CREATE option os.path.isfile(layer.dataProvider().dataSourceUri()), # is it a local file? ]): return True else: return False def set_bands_cbo(self): self.bands_cbo.currentIndexChanged.disconnect(self.update_active_bands) self.bands_cbo.clear() for band in self.handler.bands_range: self.bands_cbo.addItem(f"{band}", [band]) if self.handler.bands_nr > 1: self.bands_cbo.addItem(self.RGB, [1, 2, 3]) self.bands_cbo.setCurrentIndex(0) self.bands_cbo.currentIndexChanged.connect(self.update_active_bands) def update_active_bands(self, idx): bands = self.bands_cbo.currentData() self.handler.active_bands = bands self.spin_boxes.create_spinboxes(bands, self.handler.data_types, self.handler.nodata_values) self.color_btn.setEnabled(len(bands) > 1) self.exp_dlg_btn.setEnabled(len(bands) == 1) def set_active_raster(self): """Active layer has changed - check if it is a raster layer and prepare it for the plugin""" old_spin_boxes_values = self.spin_boxes.get_values() self.crs_transform = None layer = self.iface.activeLayer() if self.check_layer(layer): self.raster = layer self.crs_transform = None if self.project.crs() == self.raster.crs() else \ QgsCoordinateTransform(self.project.crs(), self.raster.crs(), self.project) self.handler = RasterHandler(self.raster, self.uc, self.debug) supported, unsupported_type = self.handler.write_supported() if supported: self.enable_toolbar_actions() self.set_bands_cbo() self.spin_boxes.create_spinboxes(self.handler.active_bands, self.handler.data_types, self.handler.nodata_values) if self.handler.bands_nr == len(old_spin_boxes_values): self.spin_boxes.set_values(old_spin_boxes_values) self.bands_cbo.setEnabled(self.handler.bands_nr > 1) self.color_btn.setEnabled(len(self.handler.active_bands) > 1) self.rbounds = self.raster.extent().toRectF().getCoords() self.handler.raster_changed.connect(self.add_to_undo) if self.raster.id() not in self.changes: self.changes[self.raster.id()] = RasterChanges(nr_to_keep=self.settings["undo_steps"]) else: msg = f"The raster has unsupported src_data type: {unsupported_type}" msg += "\nServal can't work with it, sorry..." self.uc.show_warn(msg) self.enable_toolbar_actions(enable=False) self.reset_raster() else: # unsupported raster self.enable_toolbar_actions(enable=False) self.reset_raster() self.check_undo_redo_btns() def add_to_undo(self, change): """Add the old and new blocks to undo stack.""" self.changes[self.raster.id()].add_change(change) self.check_undo_redo_btns() if self.logger: self.logger.debug(self.get_undo_redo_values()) def get_undo_redo_values(self): changes = self.changes[self.raster.id()] return f"nr undos: {changes.nr_undos()}, redos: {changes.nr_redos()}" def undo(self): undo_data = self.changes[self.raster.id()].undo() self.handler.write_block_undo(undo_data) self.raster.triggerRepaint() self.check_undo_redo_btns() def redo(self): redo_data = self.changes[self.raster.id()].redo() self.handler.write_block_undo(redo_data) self.raster.triggerRepaint() self.check_undo_redo_btns() def reset_raster(self): self.raster = None self.color_btn.setDisabled(True) def color_picker_connection(self, connect=True): if connect: self.color_btn.colorChanged.connect(self.set_values_from_picker) else: self.color_btn.colorChanged.disconnect(self.set_values_from_picker) @staticmethod def show_website(): QDesktopServices.openUrl(QUrl("https://github.com/lutraconsulting/serval/blob/master/Serval/docs/user_manual.md")) def recreate_spatial_index(self, layer): """Check if spatial index exists for the layer and if it is relatively old and eventually recreate it.""" ctime = self.spatial_index_time[layer.id()] if layer.id() in self.spatial_index_time else None if ctime is None or datetime.now() - ctime > timedelta(seconds=30): self.spatial_index = QgsSpatialIndex(layer.getFeatures(), None, QgsSpatialIndex.FlagStoreFeatureGeometries) self.spatial_index_time[layer.id()] = datetime.now() def get_nearest_feature(self, pt_feat, vlayer_id): """Given the point feature, return nearest feature from vlayer.""" vlayer = self.project.mapLayer(vlayer_id) self.recreate_spatial_index(vlayer) ptxy = pt_feat.geometry().asPoint() near_fid = self.spatial_index.nearestNeighbor(ptxy)[0] return vlayer.getFeature(near_fid) def nearest_feature_attr_value(self, pt_feat, vlayer_id, attr_name): """Find nearest feature to pt_feat and return its attr_name attribute value.""" near_feat = self.get_nearest_feature(pt_feat, vlayer_id) return near_feat[attr_name] def nearest_pt_on_line_interpolate_z(self, pt_feat, vlayer_id): """Find nearest line feature to pt_feat and interpolate z value from vertices.""" near_feat = self.get_nearest_feature(pt_feat, vlayer_id) near_geom = near_feat.geometry() closest_pt_dist = near_geom.lineLocatePoint(pt_feat.geometry()) closest_pt = near_geom.interpolate(closest_pt_dist) return closest_pt.get().z() def intersecting_features_attr_average(self, pt_feat, vlayer_id, attr_name, only_center): """ Find all features intersecting current feature (cell center, or raster cell polygon) and calculate average value of their attr_name attribute. """ vlayer = self.project.mapLayer(vlayer_id) self.recreate_spatial_index(vlayer) ptxy = pt_feat.geometry().asPoint() pt_x, pt_y = ptxy.x(), ptxy.y() dxy = 0.001 half_pix_x = self.handler.pixel_size_x / 2. half_pix_y = self.handler.pixel_size_y / 2. if only_center: cell = QgsRectangle(pt_x, pt_y, pt_x + dxy, pt_y + dxy) else: cell = QgsRectangle(pt_x - half_pix_x, pt_y - half_pix_y, pt_x + half_pix_x, pt_y + half_pix_y) inter_fids = self.spatial_index.intersects(cell) values = [] for fid in inter_fids: feat = vlayer.getFeature(fid) if not feat.geometry().intersects(cell): continue val = feat[attr_name] if not is_number(val): continue values.append(val) if len(values) == 0: return None return sum(values) / float(len(values)) def interpolate_from_mesh(self, pt_feat, mesh_layer_id, group, dataset, above_existing): """Interpolate from mesh.""" mesh_layer = self.project.mapLayer(mesh_layer_id) ptxy = pt_feat.geometry().asPoint() dataset_val = mesh_layer.datasetValue(QgsMeshDatasetIndex(group, dataset), ptxy) val = dataset_val.scalar() if math.isnan(val): return val if above_existing: ident_vals = self.handler.provider.identify(ptxy, QgsRaster.IdentifyFormatValue).results() org_val = list(ident_vals.values())[0] if org_val == self.handler.nodata_values[0]: return val return max(org_val, val) else: return val
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context) radius = self.parameterAsDouble(parameters, self.DISTANCE, context) horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 def searchRect(p): return QgsRectangle(p.x() - proximity, p.y() - proximity, p.x() + proximity, p.y() + proximity) index = QgsSpatialIndex() # NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm, # please port the changes to QgsPointDistanceRenderer::renderFeature also! clustered_groups = [] group_index = {} group_locations = {} for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue point = f.geometry().asPoint() other_features_within_radius = index.intersects(searchRect(point)) if not other_features_within_radius: index.insertFeature(f) group = [f] clustered_groups.append(group) group_index[f.id()] = len(clustered_groups) - 1 group_locations[f.id()] = point else: # find group with closest location to this point (may be more than one within search tolerance) min_dist_feature_id = other_features_within_radius[0] min_dist = group_locations[min_dist_feature_id].distance(point) for i in range(1, len(other_features_within_radius)): candidate_id = other_features_within_radius[i] new_dist = group_locations[candidate_id].distance(point) if new_dist < min_dist: min_dist = new_dist min_dist_feature_id = candidate_id group_index_pos = group_index[min_dist_feature_id] group = clustered_groups[group_index_pos] # calculate new centroid of group old_center = group_locations[min_dist_feature_id] group_locations[min_dist_feature_id] = QgsPointXY( (old_center.x() * len(group) + point.x()) / (len(group) + 1.0), (old_center.y() * len(group) + point.y()) / (len(group) + 1.0)) # add to a group clustered_groups[group_index_pos].append(f) group_index[f.id()] = group_index_pos feedback.setProgress(int(current * total)) current = 0 total = 100.0 / len(clustered_groups) if clustered_groups else 1 feedback.setProgress(0) fullPerimeter = 2 * math.pi for group in clustered_groups: if feedback.isCanceled(): break count = len(group) if count == 1: sink.addFeature(group[0], QgsFeatureSink.FastInsert) else: angleStep = fullPerimeter / count if count == 2 and horizontal: currentAngle = math.pi / 2 else: currentAngle = 0 old_point = group_locations[group[0].id()] for f in group: if feedback.isCanceled(): break sinusCurrentAngle = math.sin(currentAngle) cosinusCurrentAngle = math.cos(currentAngle) dx = radius * sinusCurrentAngle dy = radius * cosinusCurrentAngle # we want to keep any existing m/z values point = f.geometry().constGet().clone() point.setX(old_point.x() + dx) point.setY(old_point.y() + dy) f.setGeometry(QgsGeometry(point)) sink.addFeature(f, QgsFeatureSink.FastInsert) currentAngle += angleStep current += 1 feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featA = QgsFeature() featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount( ) and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break lstIntersectingB = [] geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: # This really shouldn't happen, as we haven't # edited the input geom at all feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): int_geom = geom.intersection(tmpGeom) lstIntersectingB.append(tmpGeom) if not int_geom: # There was a problem creating the intersection feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) int_geom = QgsGeometry() else: int_geom = QgsGeometry(int_geom) if int_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( int_geom.wkbType( )) == QgsWkbTypes.GeometryCollection: # Intersection produced different geomety types temp_list = int_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): int_geom = QgsGeometry(i) try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature( outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: # Geometry list: prevents writing error # in geometries of different types # produced by the intersection # fix #3549 if QgsWkbTypes.geometryType(int_geom.wkbType( )) == QgsWkbTypes.geometryType(geomType): try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) # the remaining bit of featA's geometry # if there is nothing left, this will just silently fail and we're good diff_geom = QgsGeometry(geom) if len(lstIntersectingB) != 0: intB = QgsGeometry.unaryUnion(lstIntersectingB) diff_geom = diff_geom.difference(intB) if diff_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( diff_geom.wkbType()) == QgsWkbTypes.GeometryCollection: temp_list = diff_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): diff_geom = QgsGeometry(i) try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) atMapA = [None] * length for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs( sourceA.sourceCrs(), context.transformContext())): if feedback.isCanceled(): break add = False geom = featA.geometry() diff_geom = QgsGeometry(geom) atMap = [None] * length atMap.extend(featA.attributes()) intersects = indexA.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(diff_geom.constGet()) engine.prepareGeometry() for featB in sourceA.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): add = True diff_geom = QgsGeometry(diff_geom.difference(tmpGeom)) else: try: # Ihis only happens if the bounding box # intersects, but the geometry doesn't diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) if add: try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] output_fields = QgsProcessingUtils.combineFields( fieldListA, fieldListB) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, output_fields, geomType, sourceA.sourceCrs()) outFeat = QgsFeature() indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs())), feedback) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1 count = 0 for featA in sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)): if feedback.isCanceled(): break if not featA.hasGeometry(): continue geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects) request.setDestinationCrs(sourceA.sourceCrs()) request.setSubsetOfAttributes(field_indices_b) engine = None if len(intersects) > 0: # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(geom.geometry()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): out_attributes = [ featA.attributes()[i] for i in field_indices_a ] out_attributes.extend( [featB.attributes()[i] for i in field_indices_b]) int_geom = QgsGeometry(geom.intersection(tmpGeom)) if int_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( int_geom.geometry().wkbType( )) == QgsWkbTypes.GeometryCollection: int_com = geom.combine(tmpGeom) int_geom = QgsGeometry() if int_com: int_sym = geom.symDifference(tmpGeom) int_geom = QgsGeometry(int_com.difference(int_sym)) if int_geom.isEmpty() or not int_geom.isGeosValid(): raise QgsProcessingException( self.tr('GEOS geoprocessing error: One or ' 'more input features have invalid ' 'geometry.')) try: if QgsWkbTypes.geometryType(int_geom.wkbType( )) == QgsWkbTypes.geometryType(geomType): int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Feature geometry error: One or more ' 'output features ignored due to invalid ' 'geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): layer = dataobjects.getObjectFromUri( self.getParameterValue(self.VECTOR)) value = float(self.getParameterValue(self.VALUE)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) strategy = self.getParameterValue(self.STRATEGY) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QGis.WKBPoint, layer.dataProvider().crs()) da = QgsDistanceArea() features = vector.features(layer) for current, f in enumerate(features): fGeom = QgsGeometry(f.geometry()) bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measure(fGeom))) index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(fGeom) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 progress.setPercentage(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog( ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) progress.setPercentage(0) del writer
def recreate_spatial_index(self, layer): """Check if spatial index exists for the layer and if it is relatively old and eventually recreate it.""" ctime = self.spatial_index_time[layer.id()] if layer.id() in self.spatial_index_time else None if ctime is None or datetime.now() - ctime > timedelta(seconds=30): self.spatial_index = QgsSpatialIndex(layer.getFeatures(), None, QgsSpatialIndex.FlagStoreFeatureGeometries) self.spatial_index_time[layer.id()] = datetime.now()
def processAlgorithm(self, parameters, context, feedback): poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) if poly_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POLYGONS)) point_source = self.parameterAsSource(parameters, self.POINTS, context) if point_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POINTS)) weight_field = self.parameterAsString(parameters, self.WEIGHT, context) weight_field_index = -1 if weight_field: weight_field_index = point_source.fields().lookupField( weight_field) class_field = self.parameterAsString(parameters, self.CLASSFIELD, context) class_field_index = -1 if class_field: class_field_index = point_source.fields().lookupField(class_field) field_name = self.parameterAsString(parameters, self.FIELD, context) fields = poly_source.fields() if fields.lookupField(field_name) < 0: fields.append(QgsField(field_name, QVariant.Int)) field_index = fields.lookupField(field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) spatialIndex = QgsSpatialIndex( point_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) point_attribute_indices = [] if weight_field_index >= 0: point_attribute_indices.append(weight_field_index) if class_field_index >= 0: point_attribute_indices.append(class_field_index) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount( ) else 0 for current, polygon_feature in enumerate(features): if feedback.isCanceled(): break count = 0 output_feature = QgsFeature() if polygon_feature.hasGeometry(): geom = polygon_feature.geometry() engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() count = 0 classes = set() points = spatialIndex.intersects(geom.boundingBox()) if len(points) > 0: request = QgsFeatureRequest().setFilterFids( points).setDestinationCrs(poly_source.sourceCrs(), context.transformContext()) request.setSubsetOfAttributes(point_attribute_indices) for point_feature in point_source.getFeatures(request): if feedback.isCanceled(): break if engine.contains( point_feature.geometry().constGet()): if weight_field_index >= 0: weight = point_feature.attributes( )[weight_field_index] try: count += float(weight) except: # Ignore fields with non-numeric values pass elif class_field_index >= 0: point_class = point_feature.attributes( )[class_field_index] if point_class not in classes: classes.add(point_class) else: count += 1 output_feature.setGeometry(geom) attrs = polygon_feature.attributes() if class_field_index >= 0: score = len(classes) else: score = count if field_index == len(attrs): attrs.append(score) else: attrs[field_index] = score output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.INTERSECT, context) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.INTERSECT_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] fieldListB = vector.testForUniqueness(fieldListA, fieldListB) for b in fieldListB: fieldListA.append(b) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fieldListA, QgsWkbTypes.Point, sourceA.sourceCrs()) spatialIndex = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback) outFeat = QgsFeature() features = sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 0 for current, inFeatA in enumerate(features): if feedback.isCanceled(): break if not inFeatA.hasGeometry(): continue inGeom = inFeatA.geometry() has_intersections = False lines = spatialIndex.intersects(inGeom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids(lines) request.setDestinationCrs(sourceA.sourceCrs()) request.setSubsetOfAttributes(field_indices_b) for inFeatB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = inFeatB.geometry() points = [] if engine.intersects(tmpGeom.geometry()): tempGeom = inGeom.intersection(tmpGeom) out_attributes = [inFeatA.attributes()[i] for i in field_indices_a] out_attributes.extend([inFeatB.attributes()[i] for i in field_indices_b]) if tempGeom.type() == QgsWkbTypes.PointGeometry: if tempGeom.isMultipart(): points = tempGeom.asMultiPoint() else: points.append(tempGeom.asPoint()) for j in points: outFeat.setGeometry(tempGeom.fromPoint(j)) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def __init__(self, geometry_type='Point'): self.geometry_type = geometry_type self._spatial_index = QgsSpatialIndex() self.ordered_dict = OrderedDict()
class FindPoints(QgsTask): def __init__(self, qpipelines, description='FindHds', debug=False): super().__init__(description, QgsTask.CanCancel) self.debug = debug if self.debug: self.hds_feature = QgsVectorLayer( 'C:/Users/jeferson.machado/Desktop/QGIS/shapes/hds_tracing.shp', "hds_tracing", "ogr") else: self.hds_feature = QgsProject.instance().mapLayersByName( 'hds_tracing')[0] self.idx_hds = QgsSpatialIndex( self.hds_feature.getFeatures(), flags=QgsSpatialIndex.FlagStoreFeatureGeometries) self.__exception = None self.q_list_pipelines = qpipelines self.list_hds = [] def find_hds_by_nearest_neighbor(self, points_vertex): hds_nearest = self.idx_hds.nearestNeighbor( point=QgsPointXY(points_vertex), neighbors=10, maxDistance=25) if len(hds_nearest) > 0: for hd in hds_nearest: if hd not in self.list_hds: self.list_hds.append(hd) def split_line(self, p1, p2, pos, pipeline): geo = QgsGeometry.fromPolyline([p1, p2]) center = geo.centroid() pipeline.insert(pos, center.asPoint()) def get_points(self, pipeline): pipe = [ QgsPointXY(pipeline.vertexAt(i)) for i in range(pipeline.get().childCount()) ] distances = [] breakForce = 0 while True: if breakForce == 1000: break # check isCanceled() to handle cancellation if self.isCanceled(): return False increment = 0 pipeline = QgsGeometry.fromMultiPointXY(pipe) for i in range(len(pipe) - 1): p1 = pipeline.vertexAt(i) p2 = pipeline.vertexAt(i + 1) d = QgsDistanceArea() distance = d.measureLine(QgsPointXY(p1), QgsPointXY(p2)) distances.append(distance) if distance > 10: self.split_line(p1, p2, i + 1 + increment, pipe) increment += 1 breakForce += 1 if distances: if max(distances) <= 10: break distances.clear() return pipeline def run(self): try: while len(self.q_list_pipelines) > 0: pipeline = self.q_list_pipelines.pop(0) pipeline_geo = self.get_points(pipeline.geometry()) for i in range(0, len(pipeline_geo.get()) - 1): self.find_hds_by_nearest_neighbor(pipeline_geo.vertexAt(i)) except Exception as e: self.__exception = e return False return True def finished(self, result): if result: self.hds_feature.selectByIds(self.list_hds) QgsMessageLog.logMessage( f"Task {self.description()} has been executed correctly\n" f"HDS: {self.list_hds}", level=Qgis.Success) else: if self.__exception is None: QgsMessageLog.logMessage( f"Tracing {self.description()} not successful " f"but without exception " f"(probably the task was manually canceled by the user)", level=Qgis.Warning) else: QgsMessageLog.logMessage( f"Task {self.description()}" f"Exception: {self.__exception}", level=Qgis.Critical) raise self.__exception def cancel(self): QgsMessageLog.logMessage( f'TracingTrask {self.description()} was canceled', level=Qgis.Info) super().cancel()
class BaseCollection(object): """Collection with same functions as fiona.collection, but which can be created in memory. Uses rtree to speedup spatial filtering""" def __init__(self, geometry_type='Point'): self.geometry_type = geometry_type self._spatial_index = QgsSpatialIndex() self.ordered_dict = OrderedDict() @property def schema(self): # todo return @property def meta(self): # todo return def filter(self, *args, **kwds): """Returns an iterator over records, but filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. """ selected = self.keys(*args, **kwds) for i in selected: if i in self.ordered_dict: yield self.ordered_dict[i] def items(self, *args, **kwds): """Returns an iterator over FID, record pairs, optionally filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. """ selected = self.keys(*args, **kwds) for i in selected: if i in self.ordered_dict: yield (i, self.ordered_dict[i]) def keys(self, start=0, stop=None, step=1, **kwds): """Returns an iterator over FIDs, optionally filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. """ selected = set(self.ordered_dict.keys()) # warning: this is not supported by Fiona if len(selected) == 0: return selected if stop is None: stop = max(selected) + 1 elif stop < 0: stop = max(0, max(selected) + stop + 1) if start is None: start = min(selected) elif start < 0: start = max(0, max(selected) + start + 1) selected.intersection_update(set(range(start, stop, step))) bbox = kwds.get('bbox') bbox_precision = kwds.get('precision', 0.0) mask = kwds.get('mask') if bbox is not None: bbox = ( bbox[0] - bbox_precision, bbox[1] - bbox_precision, bbox[2] + bbox_precision, bbox[3] + bbox_precision, ) # rtree # selected.intersection_update(set(self._spatial_index.intersection(bbox))) # qgis qbbox = QgsRectangle(*bbox) selected.intersection_update(set(self._spatial_index.intersects(qbbox))) if mask: # todo pass return selected @property def bounds(self): """Returns (minx, miny, maxx, maxy).""" # rtree # return self._spatial_index.bounds # qgis: not implemented return [None, None, None, None] def writerecords(self, records): """Stages multiple records.""" if type(records) != list: raise ValueError('list expected, got {0}'.format(type(records))) if len(self) == 0: nr = 0 else: nr = next(reversed(self.ordered_dict)) + 1 for record in records: record['id'] = nr self.ordered_dict[nr] = record # rtree # self._spatial_index.insert(nr, geom) # QGIS: feature = QgsFeature() feature.setFeatureId(nr) try: geom = shape(record['geometry']) qgeom = QgsGeometry() qgeom.fromWkb(geom.to_wkb()) except: wkt = "{}({})".format( record['geometry']['type'], ",".join(["{} {}".format(*c) for c in record['geometry']['coordinates']])) qgeom = QgsGeometry() qgeom.fromWkt(wkt) feature.setGeometry(qgeom) self._spatial_index.insertFeature(feature) nr += 1 def write(self, record): """Stages a record.""" self.writerecords([record]) def save(self, filename, crs=None, driver='ESRI Shapefile', schema=None): """ """ import fiona f = fiona.open(filename, 'w', crs=crs, driver=driver, schema=schema) records = [feat for feat in self.filter()] f.writerecords(records) f.close() # todo: check fields and append field metadata dynamicaly def __len__(self): return len(self.ordered_dict) def __getitem__(self, key): return self.ordered_dict[key] def __iter__(self): return self.filter()
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = vector.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount( ) and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs()) for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' ), self.tr('Processing'), QgsMessageLog.WARNING) continue count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs( sourceA.sourceCrs())): if feedback.isCanceled(): break geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() attrs = [NULL] * length + attrs intersects = indexA.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) for featB in sourceA.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' ), self.tr('Processing'), QgsMessageLog.WARNING) continue count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 featureCount = source.featureCount() total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs(), context.transformContext()) da.setEllipsoid(context.project().ellipsoid()) request = QgsFeatureRequest() random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break # pick random feature fid = random.randint(0, featureCount - 1) f = next( source.getFeatures( request.setFilterFid(fid).setSubsetOfAttributes([]))) fGeom = f.geometry() if fGeom.isMultipart(): lines = fGeom.asMultiPolyline() # pick random line lineId = random.randint(0, len(lines) - 1) vertices = lines[lineId] else: vertices = fGeom.asPolyline() # pick random segment if len(vertices) == 2: vid = 0 else: vid = random.randint(0, len(vertices) - 2) startPoint = vertices[vid] endPoint = vertices[vid + 1] length = da.measureLine(startPoint, endPoint) dist = length * random.random() if dist > minDistance: d = dist / (length - dist) rx = (startPoint.x() + d * endPoint.x()) / (1 + d) ry = (startPoint.y() + d * endPoint.y()) / (1 + d) # generate random point p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo( self.tr( 'Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) return {self.OUTPUT: dest_id}