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 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}
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 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): feedback.pushInfo( self.tr( "[QNEAT3Algorithm] This is a QNEAT3 Algorithm: '{}'".format( self.displayName()))) network = self.parameterAsVectorLayer(parameters, self.INPUT, context) #QgsVectorLayer startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs()) #QgsPointXY max_dist = self.parameterAsDouble(parameters, self.MAX_DIST, context) #float cell_size = self.parameterAsInt(parameters, self.CELL_SIZE, context) #int strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) #int interpolation_method = self.parameterAsEnum(parameters, self.METHOD, context) #int directionFieldName = self.parameterAsString( parameters, self.DIRECTION_FIELD, context) #str (empty if no field given) forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context) #str backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context) #str bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context) #str defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context) #int speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context) #str defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context) #float tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) #float output_path = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) analysisCrs = network.sourceCrs() input_coordinates = [startPoint] input_point = getFeatureFromPointParameter(startPoint) if analysisCrs.isGeographic(): raise QgsProcessingException( 'QNEAT3 algorithms are designed to work with projected coordinate systems. Please use a projected coordinate system (eg. UTM zones) instead of geographic coordinate systems (eg. WGS84)!' ) if analysisCrs != startPoint.sourceCrs(): raise QgsProcessingException( 'QNEAT3 algorithms require that all inputs to be the same projected coordinate reference system (including project coordinate system).' ) feedback.pushInfo("[QNEAT3Algorithm] Building Graph...") feedback.setProgress(10) net = Qneat3Network(network, input_coordinates, strategy, directionFieldName, forwardValue, backwardValue, bothValue, defaultDirection, analysisCrs, speedFieldName, defaultSpeed, tolerance, feedback) feedback.setProgress(40) analysis_point = Qneat3AnalysisPoint("point", input_point, "point_id", net, net.list_tiedPoints[0], feedback) feedback.pushInfo("[QNEAT3Algorithm] Calculating Iso-Pointcloud...") iso_pointcloud = net.calcIsoPoints([analysis_point], max_dist) feedback.setProgress(70) uri = "Point?crs={}&field=vertex_id:int(254)&field=cost:double(254,7)&field=origin_point_id:string(254)&index=yes".format( analysisCrs.authid()) iso_pointcloud_layer = QgsVectorLayer(uri, "iso_pointcloud_layer", "memory") iso_pointcloud_provider = iso_pointcloud_layer.dataProvider() iso_pointcloud_provider.addFeatures(iso_pointcloud, QgsFeatureSink.FastInsert) feedback.pushInfo( "[QNEAT3Algorithm] Calculating Iso-Interpolation-Raster using QGIS TIN-Interpolator..." ) if interpolation_method == 0: feedback.pushInfo( "[QNEAT3Algorithm] Calculating Iso-Interpolation-Raster using QGIS TIN-Interpolator..." ) net.calcIsoTinInterpolation(iso_pointcloud_layer, cell_size, output_path) feedback.setProgress(99) else: #prepare numpy coordinate grids NoData_value = -9999 raster_rectangle = iso_pointcloud_layer.extent() #implement spatial index for lines (closest line, etc...) spt_idx = QgsSpatialIndex( iso_pointcloud_layer.getFeatures(QgsFeatureRequest()), feedback) #top left point xmin = raster_rectangle.xMinimum() ymin = raster_rectangle.yMinimum() xmax = raster_rectangle.xMaximum() ymax = raster_rectangle.yMaximum() cols = int((xmax - xmin) / cell_size) rows = int((ymax - ymin) / cell_size) output_interpolation_raster = gdal.GetDriverByName('GTiff').Create( output_path, cols, rows, 1, gdal.GDT_Float64) output_interpolation_raster.SetGeoTransform( (xmin, cell_size, 0, ymax, 0, -cell_size)) band = output_interpolation_raster.GetRasterBand(1) band.SetNoDataValue(NoData_value) #initialize zero array with 2 dimensions (according to rows and cols) raster_routingcost_data = zeros(shape=(rows, cols)) #compute raster cell MIDpoints x_pos = linspace(xmin + (cell_size / 2), xmax - (cell_size / 2), raster_routingcost_data.shape[1]) y_pos = linspace(ymax - (cell_size / 2), ymin + (cell_size / 2), raster_routingcost_data.shape[0]) x_grid, y_grid = meshgrid(x_pos, y_pos) feedback.pushInfo( '[QNEAT3Network][calcQneatInterpolation] Beginning with interpolation' ) total_work = rows * cols counter = 0 feedback.pushInfo( '[QNEAT3Network][calcQneatInterpolation] Total workload: {} cells' .format(total_work)) feedback.setProgress(0) for i in range(rows): for j in range(cols): current_pixel_midpoint = QgsPointXY( x_grid[i, j], y_grid[i, j]) nearest_vertex_fid = spt_idx.nearestNeighbor( current_pixel_midpoint, 1)[0] nearest_feature = iso_pointcloud_layer.getFeature( nearest_vertex_fid) nearest_vertex = net.network.vertex( nearest_feature['vertex_id']) #yields a list of all incoming and outgoing edges edges = nearest_vertex.incomingEdges( ) + nearest_vertex.outgoingEdges() vertex_found = False nearest_counter = 2 while vertex_found == False: #find the second nearest vertex (eg, the vertex with least cost of all edges incoming to the first nearest vertex) second_nearest_feature_fid = spt_idx.nearestNeighbor( current_pixel_midpoint, nearest_counter)[nearest_counter - 1] second_nearest_feature = iso_pointcloud_layer.getFeature( second_nearest_feature_fid) second_nearest_vertex_id = second_nearest_feature[ 'vertex_id'] for edge_id in edges: from_vertex_id = net.network.edge( edge_id).fromVertex() to_vertex_id = net.network.edge(edge_id).toVertex() if second_nearest_vertex_id == from_vertex_id: vertex_found = True vertex_type = "from_vertex" from_point = second_nearest_feature.geometry( ).asPoint() from_vertex_cost = second_nearest_feature[ 'cost'] if second_nearest_vertex_id == to_vertex_id: vertex_found = True vertex_type = "to_vertex" to_point = second_nearest_feature.geometry( ).asPoint() to_vertex_cost = second_nearest_feature['cost'] nearest_counter = nearest_counter + 1 """ if nearest_counter == 5: vertex_found = True vertex_type = "end_vertex" """ if vertex_type == "from_vertex": nearest_edge_geometry = QgsGeometry().fromPolylineXY( [from_point, nearest_vertex.point()]) res = nearest_edge_geometry.closestSegmentWithContext( current_pixel_midpoint) segment_point = res[ 1] #[0: distance, 1: point, 2: left_of, 3: epsilon for snapping] dist_to_segment = segment_point.distance( current_pixel_midpoint) dist_edge = from_point.distance(segment_point) #feedback.pushInfo("dist_to_segment = {}".format(dist_to_segment)) #feedback.pushInfo("dist_on_edge = {}".format(dist_edge)) #feedback.pushInfo("cost = {}".format(from_vertex_cost)) pixel_cost = from_vertex_cost + dist_edge + dist_to_segment raster_routingcost_data[i, j] = pixel_cost elif vertex_type == "to_vertex": nearest_edge_geometry = QgsGeometry().fromPolylineXY( [nearest_vertex.point(), to_point]) res = nearest_edge_geometry.closestSegmentWithContext( current_pixel_midpoint) segment_point = res[ 1] #[0: distance, 1: point, 2: left_of, 3: epsilon for snapping] dist_to_segment = segment_point.distance( current_pixel_midpoint) dist_edge = to_point.distance(segment_point) #feedback.pushInfo("dist_to_segment = {}".format(dist_to_segment)) #feedback.pushInfo("dist_on_edge = {}".format(dist_edge)) #feedback.pushInfo("cost = {}".format(from_vertex_cost)) pixel_cost = to_vertex_cost + dist_edge + dist_to_segment raster_routingcost_data[i, j] = pixel_cost else: pixel_cost = -99999 #nearest_feature['cost'] + (nearest_vertex.point().distance(current_pixel_midpoint)) """ nearest_feature_pointxy = nearest_feature.geometry().asPoint() nearest_feature_cost = nearest_feature['cost'] dist_to_vertex = current_pixel_midpoint.distance(nearest_feature_pointxy) #implement time cost pixel_cost = dist_to_vertex + nearest_feature_cost raster_data[i,j] = pixel_cost """ counter = counter + 1 if counter % 1000 == 0: feedback.pushInfo( "[QNEAT3Network][calcQneatInterpolation] Interpolated {} cells..." .format(counter)) feedback.setProgress((counter / total_work) * 100) band.WriteArray(raster_routingcost_data) outRasterSRS = osr.SpatialReference() outRasterSRS.ImportFromWkt(net.AnalysisCrs.toWkt()) output_interpolation_raster.SetProjection( outRasterSRS.ExportToWkt()) band.FlushCache() feedback.pushInfo("[QNEAT3Algorithm] Ending Algorithm") feedback.setProgress(100) results = {} results[self.OUTPUT] = output_path return results
def linearMatrix(self, parameters, context, source, inField, target_source, targetField, same_source_and_target, matType, nPoints, feedback): if same_source_and_target: # need to fetch an extra point from the index, since the closest match will always be the same # as the input feature nPoints += 1 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()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) 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 if same_source_and_target and inFeat.id() == outFeat.id(): continue 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): 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, 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}
class PolygonsParallelToLineAlgorithm(GeoAlgorithm): OUTPUT_LAYER = 'OUTPUT_LAYER' LINE_LAYER = 'LINE_LAYER' POLYGON_LAYER = 'POLYGON_LAYER' SELECTED = 'SELECTED' WRITE_SELECTED = 'WRITE_SELECTED' LONGEST = 'LONGEST' MULTI = 'MULTI' DISTANCE = 'DISTANCE' ANGLE = 'ANGLE' COLUMN_NAME = '_rotated' def __init__(self): self._translateUi() GeoAlgorithm.__init__(self) def _translateUi(self): locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( os.path.dirname(__file__), 'i18n', 'pptl_{}.qm'.format(locale) ) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) def getIcon(self): path = os.path.join(os.path.dirname(__file__), "icons", "icon.png") return QIcon(path) def tr(self, message): className = self.__class__.__name__ return QCoreApplication.translate(className, message) def defineCharacteristics(self): # The name that the user will see in the toolbox self.name = self.tr('Polygons parallel to line') # The branch of the toolbox under which the algorithm will appear self.group = self.tr('Algorithms for vector layers') self.addOutput( OutputVector( self.OUTPUT_LAYER, 'Output layer with rotated polygons' ) ) self.addParameter( ParameterVector( self.LINE_LAYER, self.tr('Select line layer'), [ParameterVector.VECTOR_TYPE_LINE] ) ) self.addParameter( ParameterVector( self.POLYGON_LAYER, self.tr('Select polygon layer'), [ParameterVector.VECTOR_TYPE_POLYGON] ) ) self.addParameter( ParameterBoolean( self.SELECTED, self.tr('Rotate only selected polygons') ) ) self.addParameter( ParameterBoolean( self.WRITE_SELECTED, self.tr('Save only selected'), ) ) self.addParameter( ParameterBoolean( self.LONGEST, self.tr("Rotate by longest edge if both angles between " "polygon edges and line segment <= 'Angle value'") ) ) self.addParameter( ParameterBoolean( self.MULTI, self.tr("Do not rotate multipolygons") ) ) self.addParameter( ParameterNumber( self.DISTANCE, self.tr("Distance from line") ) ) self.addParameter( ParameterNumber( self.ANGLE, self.tr("Angle value"), maxValue=89.9 ) ) def processAlgorithm(self, progress): self._operationCounter = 0 self._progress = progress self._getInputValues() self._lineLayer = dataobjects.getObjectFromUri(self._lineLayerName) self._polygonLayer = dataobjects.getObjectFromUri( self._polygonLayerName ) self._createLineSpatialIndex() self._validatePolygonLayer() self._addAttribute() self._linesDict = {x.id(): x for x in self._lineLayer.getFeatures()} self._rotateAndWriteSelectedOrAll(self._getWriter()) self._deleteAttribute() def _getInputValues(self): self._lineLayerName = self.getParameterValue(self.LINE_LAYER) self._polygonLayerName = self.getParameterValue(self.POLYGON_LAYER) self._isSelected = self.getParameterValue(self.SELECTED) self._isWriteSelected = self.getParameterValue(self.WRITE_SELECTED) self._byLongest = self.getParameterValue(self.LONGEST) self._multi = self.getParameterValue(self.MULTI) self._distance = self.getParameterValue(self.DISTANCE) self._angle = self.getParameterValue(self.ANGLE) self._outputLayer = self.getOutputValue(self.OUTPUT_LAYER) def _createLineSpatialIndex(self): self._index = QgsSpatialIndex() for line in self._lineLayer.getFeatures(): self._index.insertFeature(line) def _validatePolygonLayer(self): self._totalNumber = self._polygonLayer.featureCount() if not self._totalNumber: raise GeoAlgorithmExecutionException( self.tr("Layer does not have any polygons") ) if self._isWriteSelected and not self._isSelected: raise GeoAlgorithmExecutionException( self.tr('You have chosen "Save only selected" without ' '"Rotate only selected polygons"') ) if self._isSelected: self._totalNumber = self._polygonLayer.selectedFeatureCount() if not self._totalNumber: raise GeoAlgorithmExecutionException( self.tr('You have chosen "Rotate only selected polygons" ' 'but there are no selected') ) def _addAttribute(self): for attr in self._polygonLayer.pendingFields(): if self.COLUMN_NAME == attr.name(): if attr.isNumeric(): break else: self._deleteAttribute() else: self._polygonLayer.dataProvider().addAttributes( [QgsField(self.COLUMN_NAME, QVariant.Int)] ) self._polygonLayer.updateFields() def _deleteAttribute(self): for i, attr in enumerate(self._polygonLayer.pendingFields()): if attr.name() == self.COLUMN_NAME: self._polygonLayer.dataProvider().deleteAttributes([i]) self._polygonLayer.updateFields() def _getWriter(self): settings = QSettings() systemEncoding = settings.value('/UI/encoding', 'System') provider = self._polygonLayer.dataProvider() return QgsVectorFileWriter( self._outputLayer, systemEncoding, provider.fields(), provider.geometryType(), provider.crs() ) def _rotateAndWriteSelectedOrAll(self, writer): if self._isSelected: self._rotateAndWriteSeleced(writer) else: polygons = self._polygonLayer.getFeatures() for polygon in polygons: self._rotateAndWritePolygon(polygon, writer) def _rotateAndWriteSeleced(self, writer): if self._isWriteSelected: for polygon in self._polygonLayer.selectedFeatures(): self._rotateAndWritePolygon(polygon, writer) else: selectedPolygonsIds = self._polygonLayer.selectedFeaturesIds() for p in self._polygonLayer.getFeatures(): if p.id() in selectedPolygonsIds: self._rotateAndWritePolygon(p, writer) else: writer.addFeature(p) def _rotateAndWritePolygon(self, polygon, writer): self._progressBar() self._p = polygon self._initiateRotation() writer.addFeature(self._p) def _progressBar(self): self._operationCounter += 1 currentPercentage = self._operationCounter / self._totalNumber * 100 self._progress.setPercentage(round(currentPercentage)) def _initiateRotation(self): self._getNearestLine() dist = self._nearLine.geometry().distance(self._p.geometry()) if not self._distance or dist <= self._distance: self._simpleOrMultiGeometry() def _getNearestLine(self): self._center = self._p.geometry().centroid() nearId = self._index.nearestNeighbor(self._center.asPoint(), 1) self._nearLine = self._linesDict.get(nearId[0]) def _simpleOrMultiGeometry(self): isMulti = self._p.geometry().isMultipart() if isMulti and not self._multi: dct = {} mPolygonVertexes = self._p.geometry().asMultiPolygon() for i, part in enumerate(mPolygonVertexes): minDistance, vertexIndex = self._getNearestVertex(part[0]) dct[(i, vertexIndex)] = minDistance i, vertexIndex = min(dct, key=dct.get) self._nearestEdges(mPolygonVertexes[i][0], vertexIndex) elif not isMulti: polygonVertexes = self._p.geometry().asPolygon()[0][:-1] vertexIndex = self._getNearestVertex(polygonVertexes)[1] self._nearestEdges(polygonVertexes, vertexIndex) def _getNearestVertex(self, polygonVertexes): vertexToSegmentDict = {} for vertex in polygonVertexes: vertexGeom = QgsGeometry.fromPoint(vertex) vertexToSegment = vertexGeom.distance(self._nearLine.geometry()) vertexToSegmentDict[vertexToSegment] = vertex minDistance = min(vertexToSegmentDict.keys()) self._nearestVertex = vertexToSegmentDict[minDistance] vertexIndex = polygonVertexes.index(self._nearestVertex) return minDistance, vertexIndex def _nearestEdges(self, polygonVertexes, vertexIndex): # if vertex is first if vertexIndex == 0: self._line1 = QgsGeometry.fromPolyline( [polygonVertexes[0], polygonVertexes[1]]) self._line2 = QgsGeometry.fromPolyline( [polygonVertexes[0], polygonVertexes[-1]]) # if vertex is last elif vertexIndex == len(polygonVertexes) - 1: self._line1 = QgsGeometry.fromPolyline( [polygonVertexes[-1], polygonVertexes[0]]) self._line2 = QgsGeometry.fromPolyline( [polygonVertexes[-1], polygonVertexes[-2]]) else: self._line1 = QgsGeometry.fromPolyline( [polygonVertexes[vertexIndex], polygonVertexes[vertexIndex + 1]] ) self._line2 = QgsGeometry.fromPolyline( [polygonVertexes[vertexIndex], polygonVertexes[vertexIndex - 1]] ) line1Azimuth = self._line1.asPolyline()[0].azimuth( self._line1.asPolyline()[1] ) line2Azimuth = self._line2.asPolyline()[0].azimuth( self._line2.asPolyline()[1] ) self._segmentAzimuth(line1Azimuth, line2Azimuth) def _segmentAzimuth(self, line1Azimuth, line2Azimuth): nearLineGeom = self._nearLine.geometry() if nearLineGeom.isMultipart(): dct = {} minDists = [] for line in nearLineGeom.asMultiPolyline(): l = QgsGeometry.fromPolyline(line) closestSegmContext = l.closestSegmentWithContext( self._nearestVertex ) minDists.append(closestSegmContext[0]) dct[closestSegmContext[0]] = [line, closestSegmContext[-1]] minDistance = min(minDists) closestSegment = dct[minDistance][0] indexSegmEnd = dct[minDistance][1] segmEnd = closestSegment[indexSegmEnd] segmStart = closestSegment[indexSegmEnd - 1] else: closestSegmContext = nearLineGeom.closestSegmentWithContext( self._nearestVertex ) indexSegmEnd = closestSegmContext[-1] segmEnd = nearLineGeom.asPolyline()[indexSegmEnd] segmStart = nearLineGeom.asPolyline()[indexSegmEnd - 1] segmentAzimuth = segmStart.azimuth(segmEnd) self._dltAz1 = self._getDeltaAzimuth(segmentAzimuth, line1Azimuth) self._dltAz2 = self._getDeltaAzimuth(segmentAzimuth, line2Azimuth) self._azimuth() def _getDeltaAzimuth(self, segment, line): if (segment >= 0 and line >= 0) or (segment <= 0 and line <= 0): delta = segment - line if segment > line and abs(delta) > 90: delta -= 180 elif segment < line and abs(delta) > 90: delta += 180 if 90 >= segment >= 0 and line <= 0: delta = segment + abs(line) if delta > 90: delta -= 180 elif 90 < segment and line <= 0: delta = segment - line - 180 if abs(delta) > 90: delta -= 180 if -90 <= segment <= 0 and line >= 0: delta = segment - line if abs(delta) > 90: delta += 180 elif -90 > segment and line >= 0: delta = segment - line + 180 if abs(delta) > 90: delta += 180 return delta def _azimuth(self): delta1 = abs(self._dltAz1) delta2 = abs(self._dltAz2) if abs(self._dltAz1) > 90: delta1 = 180 - delta1 if abs(self._dltAz2) > 90: delta2 = 180 - delta2 self._rotate(delta1, delta2) def _rotate(self, delta1, delta2): self._rotationCheck = True if delta1 <= self._angle and delta2 <= self._angle: if self._byLongest: self._rotateByLongest(delta1, delta2) else: self._rotateNotByLongest(delta1, delta2) else: self._othersRotations(delta1, delta2) self._markAsRotated() def _rotateByLongest(self, delta1, delta2): if delta1 <= self._angle and delta2 <= self._angle: length1 = self._line1.geometry().length() length2 = self._line2.geometry().length() if length1 >= length2: self._p.geometry().rotate(self._dltAz1, self._center.asPoint()) elif length1 < length2: self._p.geometry().rotate(self._dltAz2, self._center.asPoint()) elif length1 == length2: self._rotateNotByLongest(delta1, delta2) else: self._rotationCheck = False def _rotateNotByLongest(self, delta1, delta2): if delta1 > delta2: self._p.geometry().rotate(self._dltAz2, self._center.asPoint()) elif delta1 <= delta2: self._p.geometry().rotate(self._dltAz1, self._center.asPoint()) else: self._rotationCheck = False def _othersRotations(self, delta1, delta2): if delta1 <= self._angle: self._p.geometry().rotate(self._dltAz1, self._center.asPoint()) elif delta2 <= self._angle: self._p.geometry().rotate(self._dltAz2, self._center.asPoint()) else: self._rotationCheck = False def _markAsRotated(self): if self._rotationCheck: self._p[self.COLUMN_NAME] = 1
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ # Retrieve the feature source and sink. The 'dest_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. arrets = self.parameterAsSource(parameters, self.STOPS, context) stop_id = self.parameterAsFields(parameters, self.STOP_ID, context)[0] noeuds = self.parameterAsSource(parameters, self.NOEUDS, context) node_id = self.parameterAsFields(parameters, self.NODE_ID, context)[0] mode_i = self.parameterAsString(parameters, self.MODE_ORI, context) mode_j = self.parameterAsString(parameters, self.MODE_DES, context) texte_i = self.parameterAsString(parameters, self.TEXTE_ORI, context) texte_j = self.parameterAsString(parameters, self.TEXTE_DES, context) rayon = self.parameterAsDouble(parameters, self.RAYON, context) vitesse = self.parameterAsDouble(parameters, self.VITESSE, context) nb_max = self.parameterAsInt(parameters, self.MAX_NB, context) # Compute the number of steps to display within the progress bar and # get features from source ##a=fenetre.split(",") ##fenetre2=QgsRectangle(float(a[0]),float(a[2]),float(a[1]),float(a[3])) arr = [a for a in arrets.getFeatures()] nb = len(arr) index = QgsSpatialIndex(noeuds.getFeatures()) champs = QgsFields() champs.append(QgsField('i', QVariant.String, len=15)) champs.append(QgsField('j', QVariant.String, len=15)) champs.append(QgsField(self.tr('length'), QVariant.Double)) champs.append(QgsField(self.tr('time'), QVariant.Double)) champs.append(QgsField(self.tr('mode'), QVariant.String, len=10)) (table_connecteurs, dest_id) = self.parameterAsSink(parameters, self.CONNECTEURS, context, champs, QgsWkbTypes.LineString, noeuds.sourceCrs()) nom_fichier = dest_id fichier_connecteurs = os.path.splitext(nom_fichier)[0] + ".txt" sortie = codecs.open(fichier_connecteurs, "w", encoding="utf-8") nbc = 0 for i, n in enumerate(arr): near = index.nearestNeighbor(n.geometry().centroid().asPoint(), nb_max) feedback.setProgress(i * 100 / nb) if len(near) > 0: for k, nearest in enumerate(near): if k < nb_max: f = noeuds.getFeatures( request=QgsFeatureRequest(nearest)) for j, g in enumerate(f): if j == 0: l = n.geometry().distance(g.geometry()) id_node = unicode(g.attribute(node_id)) id_stop = unicode(n.attribute(stop_id)) if l < rayon: nbc += 1 gline = QgsGeometry.fromPolylineXY([ QgsPointXY( n.geometry().centroid().asPoint()), QgsPointXY( g.geometry().centroid().asPoint()) ]) hline = QgsGeometry.fromPolylineXY([ QgsPointXY( g.geometry().centroid().asPoint()), QgsPointXY( n.geometry().centroid().asPoint()) ]) fline = QgsFeature() fline.setGeometry(gline) ll = gline.length() moda = unicode(mode_i) + unicode(mode_j) if vitesse <= 0: fline.setAttributes([ id_stop, id_node, ll / 1000, 0.0, moda ]) else: fline.setAttributes([ id_stop, id_node, ll / 1000, ll * 60 / (vitesse * 1000), moda ]) fline2 = QgsFeature() fline2.setGeometry(hline) modb = unicode(mode_j) + unicode(mode_i) if vitesse <= 0: fline2.setAttributes([ id_node, id_stop, ll / 1000, 0, modb ]) else: fline2.setAttributes([ id_node, id_stop, ll / 1000, ll * 60 / (vitesse * 1000), modb ]) table_connecteurs.addFeature(fline) table_connecteurs.addFeature(fline2) if vitesse > 0: sortie.write(id_node + ';' + id_stop + ';' + str((60 / vitesse) * (ll / 1000.0)) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + modb + ';' + modb + '\n') sortie.write(id_stop + ';' + id_node + ';' + str((60 / vitesse) * (ll / 1000.0)) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + moda + ';' + moda + '\n') else: sortie.write(id_node + ';' + id_stop + ';' + str(0.0) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + modb + ';' + modb + '\n') sortie.write(id_stop + ';' + id_node + ';' + str(0.0) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + moda + ';' + moda + '\n') feedback.setProgressText( unicode(nbc) + "/" + unicode(nb) + self.tr(" connected nodes")) sortie.close() return {self.OUTPUT: dest_id}
def linearMatrix(self, parameters, context, source, inField, target_source, targetField, same_source_and_target, matType, nPoints, feedback): if same_source_and_target: # need to fetch an extra point from the index, since the closest match will always be the same # as the input feature nPoints += 1 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()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) 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[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 if same_source_and_target and inFeat.id() == outFeat.id(): continue outID = outFeat[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}
class Worker(QtCore.QObject): '''The worker that does the heavy lifting. /* QGIS offers spatial indexes to make spatial search more * effective. QgsSpatialIndex will find the nearest index * (approximate) geometry (rectangle) for a supplied point. * QgsSpatialIndex will give correct results when searching * for the nearest neighbour of a point in a point data set. * So something has to be done for non-point data sets * * Non-point join data set: * A two pass search is performed. First the index is used to * find the nearest index geometry (approximation - rectangle), * and then compute the actual distance to this geometry. * Then this rectangle is used to find all features in the join * data set that may be the closest feature to the given point. * For all the features is this candidate set, the actual * distance to the given point is calculated, and the nearest * feature is returned. * * Non-point input data set: * First the centroid of the non-point input geometry is * calculated. Then the index is used to find the nearest * neighbour to this point (using the approximate index * geometry). * The distance vector to this feature, combined with the * bounding rectangle of the input feature is used to create a * search rectangle to find the candidate join geometries. * For all the features is this candidate set, the actual * distance to the given feature is calculated, and the nearest * feature is returned. * * Joins involving multi-geometry data sets are not supported * by a spatial index. * */ ''' # Define the signals used to communicate back to the application progress = QtCore.pyqtSignal(float) # For reporting progress status = QtCore.pyqtSignal(str) # For reporting status error = QtCore.pyqtSignal(str) # For reporting errors #killed = QtCore.pyqtSignal() # Signal for sending over the result: finished = QtCore.pyqtSignal(bool, object) def __init__(self, inputvectorlayer, joinvectorlayer, outputlayername, approximateinputgeom, joinprefix, usejoinlayerapproximation, usejoinlayerindex): """Initialise. Arguments: inputvectorlayer -- (QgsVectorLayer) The base vector layer for the join joinvectorlayer -- (QgsVectorLayer) the join layer outputlayername -- (string) the name of the output memory layer approximateinputgeom -- (boolean) should the input geometry be approximated? Is only be set for non-single-point layers joinprefix -- (string) the prefix to use for the join layer attributes in the output layer usejoinlayerindexapproximation -- (boolean) should the index geometry approximations be used for the join? usejoinlayerindex -- (boolean) should an index for the join layer be used. Will only use the index geometry approximations for the join """ # Set a variable to control the use of indexes and exact # geometries for non-point input geometries #self.nonpointexactindex = True self.nonpointexactindex = usejoinlayerindex QtCore.QObject.__init__(self) # Essential! # Creating instance variables from the parameters self.inpvl = inputvectorlayer self.joinvl = joinvectorlayer self.outputlayername = outputlayername self.approximateinputgeom = approximateinputgeom self.joinprefix = joinprefix self.usejoinlayerapprox = usejoinlayerapproximation # Check if the layers are the same (self join) self.selfjoin = False if self.inpvl is self.joinvl: # This is a self join self.selfjoin = True # Creating instance variables for the progress bar ++ # Number of elements that have been processed - updated by # calculate_progress self.processed = 0 # Current percentage of progress - updated by # calculate_progress self.percentage = 0 # Flag set by kill(), checked in the loop self.abort = False # Number of features in the input layer - used by # calculate_progress self.feature_count = self.inpvl.featureCount() # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 def run(self): try: if self.inpvl is None or self.joinvl is None: self.status.emit('Layer is missing!') self.finished.emit(False, None) return #self.status.emit('Started!') # Check the geometry type and prepare the output layer geometryType = self.inpvl.geometryType() #self.status.emit('Input layer geometry type: ' + # str(geometryType)) geometrytypetext = 'Point' if geometryType == QGis.Point: geometrytypetext = 'Point' elif geometryType == QGis.Line: geometrytypetext = 'LineString' elif geometryType == QGis.Polygon: geometrytypetext = 'Polygon' # Does the input vector contain multi-geometries? # Try to check the first feature # This is not used for anything yet self.inputmulti = False feats = self.inpvl.getFeatures() if feats is not None: #self.status.emit('#Input features: ' + str(feats)) testfeature = feats.next() feats.rewind() feats.close() if testfeature is not None: #self.status.emit('Input feature geometry: ' + # str(testfeature.geometry())) if testfeature.geometry() is not None: if testfeature.geometry().isMultipart(): self.inputmulti = True geometrytypetext = 'Multi' + geometrytypetext else: pass else: self.status.emit('No geometry!') self.finished.emit(False, None) return else: self.status.emit('No input features!') self.finished.emit(False, None) return else: self.status.emit('getFeatures returns None for input layer!') self.finished.emit(False, None) return geomptext = geometrytypetext # Set the coordinate reference system to the input # layer's CRS if self.inpvl.crs() is not None: geomptext = (geomptext + "?crs=" + str(self.inpvl.crs().authid())) outfields = self.inpvl.pendingFields().toList() # if self.joinvl.pendingFields() is not None: jfields = self.joinvl.pendingFields().toList() for joinfield in jfields: outfields.append(QgsField(self.joinprefix + str(joinfield.name()), joinfield.type())) else: self.status.emit('Unable to get any join layer fields') #self.finished.emit(False, None) #return outfields.append(QgsField("distance", QVariant.Double)) # Create a memory layer self.mem_joinl = QgsVectorLayer(geomptext, self.outputlayername, "memory") self.mem_joinl.startEditing() for field in outfields: self.mem_joinl.dataProvider().addAttributes([field]) # For an index to be used, the input layer has to be a # point layer, the input layer geometries have to be # approximated to centroids, or the user has to have # accepted that a join layer index is used (for # non-point input layers). # (Could be extended to multipoint) if (self.inpvl.wkbType() == QGis.WKBPoint or self.inpvl.wkbType() == QGis.WKBPoint25D or self.approximateinputgeom or self.nonpointexactindex): # Create a spatial index to speed up joining self.status.emit('Creating join layer index...') self.joinlind = QgsSpatialIndex() for feat in self.joinvl.getFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.status.emit('Join layer index created!') # Does the join layer contain multi geometries? # Try to check the first feature # This is not used for anything yet self.joinmulti = False feats = self.joinvl.getFeatures() if feats is not None: testfeature = feats.next() feats.rewind() feats.close() if testfeature is not None: if testfeature.geometry() is not None: if testfeature.geometry().isMultipart(): self.joinmulti = True else: self.status.emit('No join geometry!') self.finished.emit(False, None) return else: self.status.emit('No join features!') self.finished.emit(False, None) return #if feats.next().geometry().isMultipart(): # self.joinmulti = True #feats.rewind() #feats.close() # Prepare for the join by fetching the layers into memory # Add the input features to a list inputfeatures = self.inpvl.getFeatures() self.inputf = [] for f in inputfeatures: self.inputf.append(f) # Add the join features to a list joinfeatures = self.joinvl.getFeatures() self.joinf = [] for f in joinfeatures: self.joinf.append(f) self.features = [] # Do the join! # Using the original features from the input layer for feat in self.inputf: # Allow user abort if self.abort is True: break self.do_indexjoin(feat) self.calculate_progress() self.mem_joinl.dataProvider().addFeatures(self.features) self.status.emit('Join finished') except: import traceback self.error.emit(traceback.format_exc()) self.finished.emit(False, None) if self.mem_joinl is not None: self.mem_joinl.rollBack() else: self.mem_joinl.commitChanges() if self.abort: self.finished.emit(False, None) else: self.status.emit('Delivering the memory layer...') self.finished.emit(True, self.mem_joinl) def calculate_progress(self): '''Update progress and emit a signal with the percentage''' self.processed = self.processed + 1 # update the progress bar at certain increments if (self.increment == 0 or self.processed % self.increment == 0): perc_new = (self.processed * 100) / self.feature_count if perc_new > self.percentage: self.percentage = perc_new self.progress.emit(self.percentage) def kill(self): '''Kill the thread by setting the abort flag''' self.abort = True def do_indexjoin(self, feat): '''Find the nearest neigbour using an index, if possible Parameter: feat -- The feature for which a neighbour is sought ''' infeature = feat infeatureid = infeature.id() inputgeom = QgsGeometry(infeature.geometry()) # Shall approximate input geometries be used? if self.approximateinputgeom: # Use the centroid as the input geometry inputgeom = QgsGeometry(infeature.geometry()).centroid() # Check if the coordinate systems are equal, if not, # transform the input feature! if (self.inpvl.crs() != self.joinvl.crs()): try: inputgeom.transform(QgsCoordinateTransform( self.inpvl.crs(), self.joinvl.crs())) except: import traceback self.error.emit(self.tr('CRS Transformation error!') + ' - ' + traceback.format_exc()) self.abort = True return nnfeature = None mindist = float("inf") ## Find the closest feature! if (self.approximateinputgeom or self.inpvl.wkbType() == QGis.WKBPoint or self.inpvl.wkbType() == QGis.WKBPoint25D): # The input layer's geometry type is point, or shall be # approximated to point (centroid). # Then a join index will always be used. if (self.usejoinlayerapprox or self.joinvl.wkbType() == QGis.WKBPoint or self.joinvl.wkbType() == QGis.WKBPoint25D): # The join layer's geometry type is point, or the # user wants approximate join geometries to be used. # Then the join index nearest neighbour function can # be used without refinement. if self.selfjoin: # Self join! # Have to get the two nearest neighbours nearestids = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) if nearestids[0] == infeatureid and len(nearestids) > 1: # The first feature is the same as the input # feature, so choose the second one nnfeature = self.joinvl.getFeatures( QgsFeatureRequest(nearestids[1])).next() else: # The first feature is not the same as the # input feature, so choose it nnfeature = self.joinvl.getFeatures( QgsFeatureRequest(nearestids[0])).next() ## Pick the second closest neighbour! ## (the first is supposed to be the point itself) ## Should we check for coinciding points? #nearestid = self.joinlind.nearestNeighbor( # inputgeom.asPoint(), 2)[1] #nnfeature = self.joinvl.getFeatures( # QgsFeatureRequest(nearestid)).next() else: # Not a self join, so we can search for only the # nearest neighbour (1) nearestid = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1)[0] nnfeature = self.joinvl.getFeatures( QgsFeatureRequest(nearestid)).next() mindist = inputgeom.distance(nnfeature.geometry()) elif (self.joinvl.wkbType() == QGis.WKBPolygon or self.joinvl.wkbType() == QGis.WKBPolygon25D or self.joinvl.wkbType() == QGis.WKBLineString or self.joinvl.wkbType() == QGis.WKBLineString25D): # Use the join layer index to speed up the join when # the join layer geometry type is polygon or line # and the input layer geometry type is point or an # approximation (point) nearestindexid = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1)[0] # Check for self join if self.selfjoin and nearestindexid == infeatureid: # Self join and same feature, so get the two # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) nearestindexid = nearestindexes[0] if (nearestindexid == infeatureid and len(nearestindexes) > 1): nearestindexid = nearestindexes[1] nnfeature = self.joinvl.getFeatures( QgsFeatureRequest(nearestindexid)).next() mindist = inputgeom.distance(nnfeature.geometry()) px = inputgeom.asPoint().x() py = inputgeom.asPoint().y() closefids = self.joinlind.intersects(QgsRectangle( px - mindist, py - mindist, px + mindist, py + mindist)) for closefid in closefids: if self.abort is True: break # Check for self join and same feature if self.selfjoin and closefid == infeatureid: continue closef = self.joinvl.getFeatures( QgsFeatureRequest(closefid)).next() thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Go through all the features from the join layer! for inFeatJoin in self.joinf: if self.abort is True: break joingeom = QgsGeometry(inFeatJoin.geometry()) thisdistance = inputgeom.distance(joingeom) # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break else: # non-simple point input geometries (could be multipoint) if (self.nonpointexactindex): # Use the spatial index on the join layer (default). # First we do an approximate search # Get the input geometry centroid centroid = QgsGeometry(infeature.geometry()).centroid() centroidgeom = centroid.asPoint() # Find the nearest neighbour (index geometries only) nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0] # Check for self join if self.selfjoin and nearestid == infeatureid: # Self join and same feature, so get the two # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( centroidgeom, 2) nearestid = nearestindexes[0] if nearestid == infeatureid and len(nearestindexes) > 1: nearestid = nearestindexes[1] nnfeature = self.joinvl.getFeatures( QgsFeatureRequest(nearestid)).next() mindist = inputgeom.distance(nnfeature.geometry()) # Calculate the search rectangle (inputgeom BBOX inpbbox = infeature.geometry().boundingBox() minx = inpbbox.xMinimum() - mindist maxx = inpbbox.xMaximum() + mindist miny = inpbbox.yMinimum() - mindist maxy = inpbbox.yMaximum() + mindist #minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist) #maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist) #miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist) #maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist) searchrectangle = QgsRectangle(minx, miny, maxx, maxy) # Fetch the candidate join geometries closefids = self.joinlind.intersects(searchrectangle) # Loop through the geometries and choose the closest # one for closefid in closefids: if self.abort is True: break # Check for self join and identical feature if self.selfjoin and closefid == infeatureid: continue closef = self.joinvl.getFeatures( QgsFeatureRequest(closefid)).next() thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Check all the features of the join layer! mindist = float("inf") # should not be necessary for inFeatJoin in self.joinf: if self.abort is True: break joingeom = QgsGeometry(inFeatJoin.geometry()) thisdistance = inputgeom.distance(joingeom) # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break if not self.abort: atMapA = infeature.attributes() atMapB = nnfeature.attributes() attrs = [] attrs.extend(atMapA) attrs.extend(atMapB) attrs.append(mindist) outFeat = QgsFeature() # Use the original input layer geometry!: outFeat.setGeometry(QgsGeometry(infeature.geometry())) # Use the modified input layer geometry (could be # centroid) #outFeat.setGeometry(QgsGeometry(inputgeom)) outFeat.setAttributes(attrs) self.calculate_progress() self.features.append(outFeat) #self.mem_joinl.dataProvider().addFeatures([outFeat]) 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('NNJoinEngine', message)
def processAlgorithm(self, parameters, context, feedback): layer = self.parameterAsLayer(parameters, self.Network, context) distance = parameters[self.Distance] angle = parameters[self.Angle] #Orientation Threshold Graph = {} #Store all node connections ## Preprocessing requirements params = {'INPUT':layer,'OUTPUT':'memory:'} explode = st.run("native:explodelines",params,context=context,feedback=feedback) features = explode['OUTPUT'].getFeatures(QgsFeatureRequest()) index = QgsSpatialIndex(explode['OUTPUT'].getFeatures()) data = {feature.id():feature for feature in explode['OUTPUT'].getFeatures()} total = 0 iN = 15#parameters[self.iters] #Number of neighbour points to search dI = parameters[self.dIters] #Number of distance operations to perform n = parameters[self.nodeCount] fields = QgsFields() for field in explode['OUTPUT'].fields(): fields.append(QgsField(field.name(),field.type())) (writer, dest_id) = self.parameterAsSink(parameters, self.Repaired, context, fields, QgsWkbTypes.LineString, explode['OUTPUT'].sourceCrs()) total = explode['OUTPUT'].featureCount() total = 100.0/total for enum,feature in enumerate(features): try: if total > 0: feedback.setProgress(int(enum*total)) try: geom = feature.geometry().asPolyline() except Exception: feedback.reportError(QCoreApplication.translate('Nodes','Only Linestring Layers Supported')) return {} start,end = geom[0],geom[-1] branch = [start,end] for b in branch: if b in Graph: #node count Graph[b] += 1 else: Graph[b] = 1 except Exception as e: feedback.reportError(QCoreApplication.translate('Node Error','%s'%(e))) skip = [] def calcDist(start,end): startxC,startyC = start endxC,endyC = end dx,dy = endxC-startxC,endyC-startyC dist = math.sqrt((dx**2)+(dy**2)) angle = math.degrees(math.atan2(dy,dx)) bearing = (90.0 - angle) % 360 return dist,bearing feedback.pushInfo(QCoreApplication.translate('Create Lines','Snapping Nodes')) fet = QgsFeature() addDist = distance/dI total = 100.0/dI for i in range(dI): #TO DO - remove unnecessary distance search iteration if total != -1: feedback.setProgress(int(i*total)) distance += addDist for enum,feature in enumerate(explode['OUTPUT'].getFeatures(QgsFeatureRequest())): try: geomFeat = feature.geometry() try: geom = geomFeat.asPolyline() except Exception: feedback.reportError(QCoreApplication.translate('Nodes','Only Linestring Layers Supported')) return {} start,end = geom[0],geom[-1] branch = [start,end] startx,starty = start endx,endy = end fID = feature.id() vertices = [Graph[branch[0]],Graph[branch[1]]] rows = [] for field in explode['OUTPUT'].fields(): rows.append(feature[field.name()]) dist, dist2 = 1e10,1e10 if vertices[0] == 1: near = index.nearestNeighbor(QgsPointXY(startx,starty), iN) #look for x closest lines dist = 1e10 for nid in near: nearFeat = data[nid] inFID = nearFeat.id() if inFID != fID: ##TO DO SIMPLIFY## try: nearGeom = nearFeat.geometry().asPolyline() except Exception: nearGeom = nearFeat.geometry().asMultiPolyline()[0] start2,end2 = nearGeom[0],nearGeom[-1] featDist,featOrient = calcDist(end,start) curDist,curOrient = calcDist(start,start2) curDist2,curOrient2 = calcDist(start2,end2) curDiff = 180 - abs(abs(featOrient - curOrient) - 180) curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180) v = Graph[start2] if curDist < dist and curDiff < angle and curDiff2 < angle: if v <= n: dist = curDist outB = start2 outFID = inFID curDist,curOrient = calcDist(start,end2) curDist2,curOrient2 = calcDist(end2,start2) curDiff = 180 - abs(abs(featOrient - curOrient) - 180) curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180) v = Graph[end2] if curDist < dist and curDiff < angle and curDiff2 < angle: if v <= n: dist = curDist outB = end2 outFID = inFID if dist < distance: if outB in skip or start in skip: continue else: points = [QgsPointXY(outB[0],outB[1]),QgsPointXY(startx,starty)] outGeom = QgsGeometry.fromPolylineXY(points) for nid in near: nearFeat = data[nid] inFID = nearFeat.id() if inFID == fID or inFID == outFID: continue else: nearGeom = nearFeat.geometry() geom = nearGeom.asPolyline() if geom[0] == outB or geom[1] == outB: continue elif outGeom.intersects(nearGeom): outB = None break if outB: skip.extend([outB,start]) fet.setGeometry(outGeom) fet.setAttributes(rows) writer.addFeature(fet,QgsFeatureSink.FastInsert) v = Graph[outB] Graph[outB] = v + 1 if vertices[1] == 1: near = index.nearestNeighbor(QgsPointXY(endx,endy), iN) #look for x closest lines dist2 = 1e10 for nid in near: nearFeat = data[nid] inFID = nearFeat.id() if inFID != fID: try: nearGeom = nearFeat.geometry().asPolyline() except Exception: nearGeom = nearFeat.geometry().asMultiPolyline()[0] start2,end2 = nearGeom[0],nearGeom[-1] if ((start2,end)) in skip: continue featDist,featOrient = calcDist(start,end) curDist,curOrient = calcDist(end,start2) curDist2,curOrient2 = calcDist(start2,end2) curDiff = 180 - abs(abs(featOrient - curOrient) - 180) curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180) v = Graph[start2] if curDist < dist2 and curDiff < angle and curDiff2 < angle: if v <= n: dist2 = curDist outB = start2 outFID = inFID curDist,curOrient = calcDist(end,end2) curDist2,curOrient2 = calcDist(end2,start2) curDiff = 180 - abs(abs(featOrient - curOrient) - 180) curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180) v = Graph[end2] if curDist < dist2 and curDiff < angle and curDiff2 < angle: if v <= n: dist2 = curDist outB = end2 outFID = inFID if dist2 < distance: if outB in skip or end in skip: continue else: points = [QgsPointXY(outB[0],outB[1]),QgsPointXY(endx,endy)] outGeom = QgsGeometry.fromPolylineXY(points) for nid in near: nearFeat = data[nid] inFID = nearFeat.id() if inFID == fID or inFID == outFID: continue else: nearGeom = nearFeat.geometry() geom = nearGeom.asPolyline() if geom[0] == outB or geom[1] == outB: continue elif outGeom.intersects(nearGeom): outB = None break if outB: skip.extend([outB,end]) outGeom = QgsGeometry.fromPolylineXY(points) fet.setGeometry(outGeom) fet.setAttributes(rows) writer.addFeature(fet,QgsFeatureSink.FastInsert) Graph[outB] = v + 1 except Exception as e: feedback.reportError(QCoreApplication.translate('Node Error','%s'%(e))) continue total = 100.0/enum for enum,feature in enumerate(explode['OUTPUT'].getFeatures(QgsFeatureRequest())): try: if total != -1: feedback.setProgress(int(enum*total)) geomFeat = feature.geometry() rows = [] for field in explode['OUTPUT'].fields(): rows.append(feature[field.name()]) fet.setGeometry(geomFeat) fet.setAttributes(rows) writer.addFeature(fet,QgsFeatureSink.FastInsert) except Exception as e: feedback.reportError(QCoreApplication.translate('Node Error','%s'%(e))) continue return {self.Repaired: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)) name_field_name = self.parameterAsString(parameters, self.NAME_FIELD, context) elevation_field_name = self.parameterAsString(parameters, self.ELEVATION_FIELD, context) name_field_index = source.fields().lookupField(name_field_name) elevation_field_index = source.fields().lookupField( elevation_field_name) request = QgsFeatureRequest() request.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'), context.transformContext()) expression_string = self.parameterAsString(parameters, self.ORDERBY_EXPRESSION, context) if expression_string: expression = QgsExpression(expression_string) if expression.hasParserError(): raise QgsProcessingException(expression.parserErrorString()) request.addOrderBy(expression_string) departure_point = QgsPointXY() destination_point = QgsPointXY() user_points = '' total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures(request) for current, feature in enumerate(features): if feedback.isCanceled(): break if not feature.hasGeometry(): continue point = feature.geometry().asPoint() if departure_point.isEmpty(): departure_point = point destination_point = point name = 'UNTITLED' if name_field_index >= 0: name = feature[name_field_index] name = re.sub('[^0-9A-Za-z_]', '', re.sub('\s+', '_', name)) elevation = 1000.0 if elevation_field_index >= 0: elevation = feature[elevation_field_index] user_points += ' <ATCWaypoint id="{}">\n <ATCWaypointType>User</ATCWaypointType>\n <WorldPosition>{}</WorldPosition>\n </ATCWaypoint>\n'.format( name, self.formattedCoordinateElevation(point, elevation)) feedback.setProgress(int(current * total)) if departure_point.isEmpty(): raise QgsProcessingException('Error: departure point is missing') if destination_point.isEmpty(): raise QgsProcessingException('Error: destination point is missing') strips = QgsVectorLayer( os.path.join(os.path.dirname(__file__), 'data', 'strips.gpkg'), 'strips') index = QgsSpatialIndex(strips.getFeatures()) icao_index = strips.fields().lookupField('icao') name_short_index = strips.fields().lookupField('nameshort') departure_airport = self.parameterAsString(parameters, self.DEPARTURE_AIRPORT, context) if departure_airport: feature = QgsFeature() expression = QgsExpression( "icao ILIKE '{}'".format(departure_airport)) strips.getFeatures( QgsFeatureRequest(expression)).nextFeature(feature) if feature: departure_point = feature.geometry().asPoint() else: raise QgsProcessingException( 'Error: custom departure airport ICAO ID not found') destination_airport = self.parameterAsString(parameters, self.DESTINATION_AIRPORT, context) if destination_airport: feature = QgsFeature() expression = QgsExpression( "icao ILIKE '{}'".format(destination_airport)) strips.getFeatures( QgsFeatureRequest(expression)).nextFeature(feature) if feature: destination_point = feature.geometry().asPoint() else: raise QgsProcessingException( 'Error: custom destination airport ICAO ID not found') departure = '' departure_header = '' destination = '' destination_header = '' if not departure_point.isEmpty(): departure_id = index.nearestNeighbor(departure_point) feature = strips.getFeature(departure_id[0]) point = feature.geometry().asPoint() elevation = 50.0 departure = ' <ATCWaypoint id="{}">\n <ATCWaypointType>Airport</ATCWaypointType>\n <WorldPosition>{}</WorldPosition>\n <RunwayNumberFP>1</RunwayNumberFP>\n <ICAO>\n <ICAOIdent>{}</ICAOIdent>\n </ICAO>\n </ATCWaypoint>\n'.format( feature[icao_index], self.formattedCoordinateElevation(point, elevation), feature[icao_index]) departure_header = ' <DepartureID>{}</DepartureID>\n <DepartureLLA>{}</DepartureLLA>\n <DepartureName>{}</DepartureName>\n'.format( feature[icao_index], self.formattedCoordinateElevation(point, elevation), feature[name_short_index]) if not destination_point.isEmpty(): destination_id = index.nearestNeighbor(destination_point) feature = strips.getFeature(destination_id[0]) point = feature.geometry().asPoint() elevation = 50.0 destination = ' <ATCWaypoint id="{}">\n <ATCWaypointType>Airport</ATCWaypointType>\n <WorldPosition>{}</WorldPosition>\n <RunwayNumberFP>1</RunwayNumberFP>\n <ICAO>\n <ICAOIdent>{}</ICAOIdent>\n </ICAO>\n </ATCWaypoint>\n'.format( feature[icao_index], self.formattedCoordinateElevation(point, elevation), feature[icao_index]) destination_header = ' <DestinationID>{}</DestinationID>\n <DestinationLLA>{}</DestinationLLA>\n <DestinationName>{}</DestinationName>\n'.format( feature[icao_index], self.formattedCoordinateElevation(point, elevation), feature[name_short_index]) title = self.parameterAsString(parameters, self.TITLE, context) plan_file_path = self.parameterAsString(parameters, self.OUTPUT, context) plan_file = open(plan_file_path, 'w') plan_file.write( '<?xml version="1.0" encoding="UTF-8"?>\n\n<SimBase.Document Type="AceXML" version="1,0">\n <Descr>AceXML Document</Descr>\n <FlightPlan.FlightPlan>\n <Title>{}</Title>\n <FPType>IFR</FPType>\n <RouteType>LowAlt</RouteType>\n <CruisingAlt>11000.000</CruisingAlt>\n' .format(title)) plan_file.write(departure_header + destination_header) plan_file.write( ' <Descr>{}</Descr>\n <AppVersion>\n <AppVersionMajor>11</AppVersionMajor>\n <AppVersionBuild>282174</AppVersionBuild>\n </AppVersion>\n' .format(title)) plan_file.write(departure + user_points + destination) plan_file.write(' </FlightPlan.FlightPlan>\n</SimBase.Document>\n') plan_file.close() return {self.OUTPUT: plan_file_path}
def processAlgorithm(self, parameters, context, feedback): try: import math, random, string import pandas as pd import processing as st import networkx as nx import numpy as np except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) feedback.reportError(QCoreApplication.translate('Error',' ')) feedback.reportError(QCoreApplication.translate('Error','Error loading modules - please install the necessary dependencies')) return {} Network = self.parameterAsLayer(parameters, self.Centerline, context) Sources = self.parameterAsLayer(parameters, self.Sources, context) Targets = self.parameterAsLayer(parameters, self.Targets, context) Precision = 6 explode = st.run("native:explodelines",{'INPUT':Network,'OUTPUT':'memory:'},context=context,feedback=feedback) wF = parameters[self.Weight] fet = QgsFeature() fs = QgsFields() fs = QgsFields() fs.append(QgsField("ID", QVariant.Int)) field_names = ['Distance', 'SP_Dist'] for name in field_names: fs.append(QgsField(name, QVariant.Double)) (writer, dest_id) = self.parameterAsSink(parameters, self.Tortuosity, context, fs, QgsWkbTypes.LineString, Network.sourceCrs()) index = QgsSpatialIndex(explode['OUTPUT'].getFeatures()) orig_data = {feature.id():feature for feature in explode['OUTPUT'].getFeatures()} srcs,tgts = {},{} field_check = Sources.fields().indexFromName('ID') if field_check == -1: feedback.reportError(QCoreApplication.translate('Error','No ID attribute in Source layer')) return {} if Targets: field_check2 = Targets.fields().indexFromName('ID') if field_check2 == -1: feedback.reportError(QCoreApplication.translate('Error','No ID attribute in Targets layer')) return {} feedback.pushInfo(QCoreApplication.translate('Model','Defining Source Nodes')) total = 100.0/Sources.featureCount() c = 0 for enum,feature in enumerate(Sources.getFeatures()): #Find source node try: if total > 0: feedback.setProgress(int(enum*total)) pnt = feature.geometry().asPoint() startx,starty = (round(pnt.x(),Precision),round(pnt.y(),Precision)) featFIDs = index.nearestNeighbor(QgsPointXY(startx,starty), 2) d = 1e10 ID = None for FID in featFIDs: feature2 = orig_data[FID] testGeom = QgsGeometry.fromPointXY(QgsPointXY(startx,starty)) dist = QgsGeometry.distance(testGeom,feature2.geometry()) if dist < d: #Find closest vertex in graph to source point ID = feature['ID'] d = dist geom = feature2.geometry() start,end = geom.asPolyline() startx2,starty2 = (round(start.x(),Precision),round(start.y(),Precision)) endx2,endy2 = (round(end.x(),Precision),round(end.y(),Precision)) testGeom2 = QgsGeometry.fromPointXY(QgsPointXY(startx2,starty2)) testGeom3 = QgsGeometry.fromPointXY(QgsPointXY(endx2,endy2)) near = QgsGeometry.distance(testGeom2,testGeom) near2 = QgsGeometry.distance(testGeom3,testGeom) if near < near2: x,y = startx2,starty2 else: x,y = endx2,endy2 if ID: if ID in srcs: srcs[ID].append((x,y)) else: srcs[ID] = [(x,y)] c+=1 except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) if Targets: total = 100.0/Targets.featureCount() feedback.pushInfo(QCoreApplication.translate('Model','Defining Target Nodes')) for enum,feature in enumerate(Targets.getFeatures()): #Find source node try: if total > 0: feedback.setProgress(int(enum*total)) pnt = feature.geometry().asPoint() startx,starty = (round(pnt.x(),Precision),round(pnt.y(),Precision)) featFIDs = index.nearestNeighbor(QgsPointXY(startx,starty), 2) d = 1e10 ID = None for FID in featFIDs: feature2 = orig_data[FID] testGeom = QgsGeometry.fromPointXY(QgsPointXY(startx,starty)) dist = QgsGeometry.distance(testGeom,feature2.geometry()) if dist < d: #Find closest vertex in graph to source point ID = feature['ID'] d = dist geom = feature2.geometry() start,end = geom.asPolyline() startx2,starty2 = (round(start.x(),Precision),round(start.y(),Precision)) endx2,endy2 = (round(end.x(),Precision),round(end.y(),Precision)) testGeom2 = QgsGeometry.fromPointXY(QgsPointXY(startx2,starty2)) testGeom3 = QgsGeometry.fromPointXY(QgsPointXY(endx2,endy2)) near = QgsGeometry.distance(testGeom2,testGeom) near2 = QgsGeometry.distance(testGeom3,testGeom) if near < near2: x,y = startx2,starty2 else: x,y = endx2,endy2 if ID: if ID in tgts: tgts[ID].append((x,y)) else: tgts[ID] = [(x,y)] c+=1 except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) total = 100.0/Network.featureCount() G = nx.Graph() feedback.pushInfo(QCoreApplication.translate('Model','Building Graph')) for enum,feature in enumerate(explode['OUTPUT'].getFeatures()): #Build Graph try: if total > 0: feedback.setProgress(int(enum*total)) geom = feature.geometry() if geom.isMultipart(): start,end = geom.asMultiPolyline()[0] else: start,end = geom.asPolyline() startx,starty = (round(start.x(),Precision),round(start.y(),Precision)) endx,endy = (round(end.x(),Precision),round(end.y(),Precision)) if wF: w = feature[wF] if type(w) == int or type(w) == float: pass else: feedback.reportError(QCoreApplication.translate('Error','Weight field contains non numeric values - check for null values')) return {} w = float(W)*feature.geometry().length() else: w = feature.geometry().length() G.add_edge((startx,starty),(endx,endy),weight=w) #G.add_edge((endx,endy),(startx,starty),weight=w) except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) feedback.pushInfo(QCoreApplication.translate('Model','Creating Fracture Network')) try: for FID in srcs: sources = srcs[FID] for source in sources: if G.has_node(source): if FID in tgts: targets = tgts[FID] for target in targets: try: path = nx.single_source_dijkstra(G, source, target, weight='weight') except Exception: feedback.reportError(QCoreApplication.translate('Error','No connection found between source and target of ID %s'%(FID))) continue length = nx.single_source_dijkstra_path_length(G,source) sx = None Index = 1 for p in path[Index]: if sx == None: sx, sy = p[0], p[1] continue ex, ey = p[0], p[1] D = max([length[(sx, sy)], length[(ex, ey)]]) dx = path[Index][0][0] - ex dy = path[Index][0][1] - ey dx2 = path[Index][0][0] - sx dy2 = path[Index][0][1] - sy SP = max([math.sqrt((dx ** 2) + (dy ** 2)), math.sqrt((dx2 ** 2) + (dy2 ** 2))]) points = [QgsPointXY(sx, sy), QgsPointXY(ex, ey)] fet.setGeometry(QgsGeometry.fromPolylineXY(points)) fet.setAttributes([FID, D, SP]) writer.addFeature(fet) sx, sy = ex, ey else: length, path = nx.single_source_dijkstra(G, source, weight='weight') sx = None Index = max(length, key=length.get) source = path[Index][-1] for p in path[Index]: if sx == None: sx, sy = p[0], p[1] continue ex, ey = p[0], p[1] D = max([length[(sx, sy)], length[(ex, ey)]]) dx = path[Index][0][0] - ex dy = path[Index][0][1] - ey dx2 = path[Index][0][0] - sx dy2 = path[Index][0][1] - sy SP = max([math.sqrt((dx ** 2) + (dy ** 2)), math.sqrt((dx2 ** 2) + (dy2 ** 2))]) points = [QgsPointXY(sx, sy), QgsPointXY(ex, ey)] fet.setGeometry(QgsGeometry.fromPolylineXY(points)) fet.setAttributes([FID, D, SP]) writer.addFeature(fet) sx, sy = ex, ey except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) return {self.Tortuosity:dest_id}
class Worker(QtCore.QObject): '''The worker that does the heavy lifting. /* QGIS offers spatial indexes to make spatial search more * effective. QgsSpatialIndex will find the nearest index * (approximate) geometry (rectangle) for a supplied point. * QgsSpatialIndex will only give correct results when searching * for the nearest neighbour of a point in a point data set. * So something has to be done for non-point data sets * * Non-point join data set: * A two pass search is performed. First the index is used to * find the nearest index geometry (approximation - rectangle), * and then compute the distance to the actual indexed geometry. * A rectangle is constructed from this (maximum minimum) * distance, and this rectangle is used to find all features in * the join data set that may be the closest feature to the given * point. * For all the features is this candidate set, the actual * distance to the given point is calculated, and the nearest * feature is returned. * * Non-point input data set: * First the centroid of the non-point input geometry is * calculated. Then the index is used to find the nearest * neighbour to this point (using the approximate index * geometry). * The distance vector to this feature, combined with the * bounding rectangle of the input feature is used to create a * search rectangle to find the candidate join geometries. * For all the features is this candidate set, the actual * distance to the given feature is calculated, and the nearest * feature is returned. * * Joins involving multi-geometry data sets are not supported * by a spatial index. * */ ''' # Define the signals used to communicate back to the application progress = QtCore.pyqtSignal(float) # For reporting progress status = QtCore.pyqtSignal(str) # For reporting status error = QtCore.pyqtSignal(str) # For reporting errors # Signal for sending over the result: finished = QtCore.pyqtSignal(bool, object) def __init__(self, inputvectorlayer, joinvectorlayer, outputlayername, joinprefix, distancefieldname="distance", approximateinputgeom=False, usejoinlayerapproximation=False, usejoinlayerindex=True, selectedinputonly=True, selectedjoinonly=True): """Initialise. Arguments: inputvectorlayer -- (QgsVectorLayer) The base vector layer for the join joinvectorlayer -- (QgsVectorLayer) the join layer outputlayername -- (string) the name of the output memory layer joinprefix -- (string) the prefix to use for the join layer attributes in the output layer distancefieldname -- name of the (new) field where neighbour distance is stored approximateinputgeom -- (boolean) should the input geometry be approximated? Is only be set for non-single-point layers usejoinlayerindexapproximation -- (boolean) should the index geometry approximations be used for the join? usejoinlayerindex -- (boolean) should an index for the join layer be used. """ QtCore.QObject.__init__(self) # Essential! # Set a variable to control the use of indexes and exact # geometries for non-point input geometries self.nonpointexactindex = usejoinlayerindex # Creating instance variables from the parameters self.inpvl = inputvectorlayer self.joinvl = joinvectorlayer self.outputlayername = outputlayername self.joinprefix = joinprefix self.approximateinputgeom = approximateinputgeom self.usejoinlayerapprox = usejoinlayerapproximation self.selectedinonly = selectedinputonly self.selectedjoonly = selectedjoinonly # Check if the layers are the same (self join) self.selfjoin = False if self.inpvl is self.joinvl: # This is a self join self.selfjoin = True # The name of the attribute for the calculated distance self.distancename = distancefieldname # Creating instance variables for the progress bar ++ # Number of elements that have been processed - updated by # calculate_progress self.processed = 0 # Current percentage of progress - updated by # calculate_progress self.percentage = 0 # Flag set by kill(), checked in the loop self.abort = False # Number of features in the input layer - used by # calculate_progress (set when needed) self.feature_count = 1 # The number of elements that is needed to increment the # progressbar (set when needed) self.increment = 0 def run(self): try: # Check if the layers look OK if self.inpvl is None or self.joinvl is None: self.status.emit('Layer is missing!') self.finished.emit(False, None) return # Check if there are features in the layers incount = 0 if self.selectedinonly: incount = self.inpvl.selectedFeatureCount() else: incount = self.inpvl.featureCount() joincount = 0 if self.selectedjoonly: joincount = self.joinvl.selectedFeatureCount() else: joincount = self.joinvl.featureCount() if incount == 0 or joincount == 0: self.status.emit('Layer without features!') self.finished.emit(False, None) return # Check the geometry type and prepare the output layer geometryType = self.inpvl.geometryType() geometrytypetext = 'Point' if geometryType == QgsWkbTypes.PointGeometry: geometrytypetext = 'Point' elif geometryType == QgsWkbTypes.LineGeometry: geometrytypetext = 'LineString' elif geometryType == QgsWkbTypes.PolygonGeometry: geometrytypetext = 'Polygon' # Does the input vector contain multi-geometries? # Try to check the first feature # This is not used for anything yet self.inputmulti = False if self.selectedinonly: #feats = self.inpvl.selectedFeaturesIterator() feats = self.inpvl.getSelectedFeatures() else: feats = self.inpvl.getFeatures() if feats is not None: testfeature = next(feats) feats.rewind() feats.close() if testfeature is not None: if testfeature.hasGeometry(): if testfeature.geometry().isMultipart(): self.inputmulti = True geometrytypetext = 'Multi' + geometrytypetext else: pass else: self.status.emit('No geometry!') self.finished.emit(False, None) return else: self.status.emit('No input features!') self.finished.emit(False, None) return else: self.status.emit('getFeatures returns None for input layer!') self.finished.emit(False, None) return geomttext = geometrytypetext # Set the coordinate reference system to the input # layer's CRS using authid (proj4 may be more robust) if self.inpvl.crs() is not None: geomttext = (geomttext + "?crs=" + str(self.inpvl.crs().authid())) # Retrieve the fields from the input layer outfields = self.inpvl.pendingFields().toList() # Retrieve the fields from the join layer if self.joinvl.pendingFields() is not None: jfields = self.joinvl.pendingFields().toList() for joinfield in jfields: outfields.append( QgsField(self.joinprefix + str(joinfield.name()), joinfield.type())) else: self.status.emit('Unable to get any join layer fields') # Add the nearest neighbour distance field # Check if there is already a "distance" field # (should be avoided in the user interface) # Try a new name if there is a collission collission = True while collission: # Iterate until there are no collissions collission = False for field in outfields: if field.name() == self.distancename: self.status.emit( 'Distance field already exists - renaming!') #self.abort = True #self.finished.emit(False, None) #break collission = True self.distancename = self.distancename + '1' outfields.append(QgsField(self.distancename, QVariant.Double)) # Create a memory layer using a CRS description self.mem_joinl = QgsVectorLayer(geomttext, self.outputlayername, "memory") # Set the CRS to the inputlayer's CRS self.mem_joinl.setCrs(self.inpvl.crs()) self.mem_joinl.startEditing() # Add the fields for field in outfields: self.mem_joinl.dataProvider().addAttributes([field]) # For an index to be used, the input layer has to be a # point layer, the input layer geometries have to be # approximated to centroids, or the user has to have # accepted that a join layer index is used (for # non-point input layers). # (Could be extended to multipoint) if (self.inpvl.wkbType() == QgsWkbTypes.Point or self.inpvl.wkbType() == QgsWkbTypes.Point25D or self.approximateinputgeom or self.nonpointexactindex): # Create a spatial index to speed up joining self.status.emit('Creating join layer index...') # Number of features in the input layer - used by # calculate_progress if self.selectedjoonly: self.feature_count = self.joinvl.selectedFeatureCount() else: self.feature_count = self.joinvl.featureCount() # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 self.joinlind = QgsSpatialIndex() if self.selectedjoonly: #for feat in self.joinvl.selectedFeaturesIterator(): for feat in self.joinvl.getSelectedFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.calculate_progress() else: for feat in self.joinvl.getFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.calculate_progress() self.status.emit('Join layer index created!') self.processed = 0 self.percentage = 0 #self.calculate_progress() # Does the join layer contain multi geometries? # Try to check the first feature # This is not used for anything yet self.joinmulti = False if self.selectedjoonly: feats = self.joinvl.getSelectedFeatures() else: feats = self.joinvl.getFeatures() if feats is not None: testfeature = next(feats) feats.rewind() feats.close() if testfeature is not None: if testfeature.hasGeometry(): if testfeature.geometry().isMultipart(): self.joinmulti = True else: self.status.emit('No join geometry!') self.finished.emit(False, None) return else: self.status.emit('No join features!') self.finished.emit(False, None) return # Prepare for the join by fetching the layers into memory # Add the input features to a list self.inputf = [] if self.selectedinonly: for f in self.inpvl.getSelectedFeatures(): self.inputf.append(f) else: for f in self.inpvl.getFeatures(): self.inputf.append(f) # Add the join features to a list self.joinf = [] if self.selectedjoonly: for f in self.joinvl.getSelectedFeatures(): self.joinf.append(f) else: for f in self.joinvl.getFeatures(): self.joinf.append(f) self.features = [] # Do the join! # Number of features in the input layer - used by # calculate_progress if self.selectedinonly: self.feature_count = self.inpvl.selectedFeatureCount() else: self.feature_count = self.inpvl.featureCount() # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 # Using the original features from the input layer for feat in self.inputf: # Allow user abort if self.abort is True: break self.do_indexjoin(feat) self.calculate_progress() self.mem_joinl.dataProvider().addFeatures(self.features) self.status.emit('Join finished') except: import traceback self.error.emit(traceback.format_exc()) self.finished.emit(False, None) if self.mem_joinl is not None: self.mem_joinl.rollBack() else: self.mem_joinl.commitChanges() if self.abort: self.finished.emit(False, None) else: self.status.emit('Delivering the memory layer...') self.finished.emit(True, self.mem_joinl) def calculate_progress(self): '''Update progress and emit a signal with the percentage''' self.processed = self.processed + 1 # update the progress bar at certain increments if (self.increment == 0 or self.processed % self.increment == 0): # Calculate percentage as integer perc_new = (self.processed * 100) / self.feature_count if perc_new > self.percentage: self.percentage = perc_new self.progress.emit(self.percentage) def kill(self): '''Kill the thread by setting the abort flag''' self.abort = True def do_indexjoin(self, feat): '''Find the nearest neigbour using an index, if possible Parameter: feat -- The feature for which a neighbour is sought ''' infeature = feat # Get the feature ID infeatureid = infeature.id() # Get the feature geometry inputgeom = infeature.geometry() # Shall approximate input geometries be used? if self.approximateinputgeom: # Use the centroid as the input geometry inputgeom = infeature.geometry().centroid() # Check if the coordinate systems are equal, if not, # transform the input feature! if (self.inpvl.crs() != self.joinvl.crs()): try: inputgeom.transform( QgsCoordinateTransform(self.inpvl.crs(), self.joinvl.crs())) except: import traceback self.error.emit( self.tr('CRS Transformation error!') + ' - ' + traceback.format_exc()) self.abort = True return ## Find the closest feature! nnfeature = None mindist = float("inf") if (self.approximateinputgeom or self.inpvl.wkbType() == QgsWkbTypes.Point or self.inpvl.wkbType() == QgsWkbTypes.Point25D): # The input layer's geometry type is point, or has been # approximated to point (centroid). # Then a join index will always be used. if (self.usejoinlayerapprox or self.joinvl.wkbType() == QgsWkbTypes.Point or self.joinvl.wkbType() == QgsWkbTypes.Point25D): # The join index nearest neighbour function can # be used without refinement. if self.selfjoin: # Self join! # Have to get the two nearest neighbours nearestids = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) if nearestids[0] == infeatureid and len(nearestids) > 1: # The first feature is the same as the input # feature, so choose the second one if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestids[1]))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestids[1]))) else: # The first feature is not the same as the # input feature, so choose it if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestids[0]))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestids[0]))) else: # Not a self join, so we can search for only the # nearest neighbour (1) nearestid = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1)[0] if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestid))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestid))) mindist = inputgeom.distance(nnfeature.geometry()) elif (self.joinvl.wkbType() == QgsWkbTypes.Polygon or self.joinvl.wkbType() == QgsWkbTypes.Polygon25D or self.joinvl.wkbType() == QgsWkbTypes.LineString or self.joinvl.wkbType() == QgsWkbTypes.LineString25D): # Use the join layer index to speed up the join when # the join layer geometry type is polygon or line # and the input layer geometry type is point or an # approximation (point) nearestindexid = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1)[0] # Check for self join if self.selfjoin and nearestindexid == infeatureid: # Self join and same feature, so get the # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) nearestindexid = nearestindexes[0] if (nearestindexid == infeatureid and len(nearestindexes) > 1): nearestindexid = nearestindexes[1] if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestindexid))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestindexid))) mindist = inputgeom.distance(nnfeature.geometry()) px = inputgeom.asPoint().x() py = inputgeom.asPoint().y() closefids = self.joinlind.intersects( QgsRectangle(px - mindist, py - mindist, px + mindist, py + mindist)) for closefid in closefids: if self.abort is True: break # Check for self join and same feature if self.selfjoin and closefid == infeatureid: continue if self.selectedjoonly: closef = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(closefid))) else: closef = next( self.joinvl.getFeatures( QgsFeatureRequest(closefid))) thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Go through all the features from the join layer! for inFeatJoin in self.joinf: if self.abort is True: break joingeom = inFeatJoin.geometry() thisdistance = inputgeom.distance(joingeom) # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break else: # non-simple point input geometries (could be multipoint) if (self.nonpointexactindex): # Use the spatial index on the join layer (default). # First we do an approximate search # Get the input geometry centroid centroid = infeature.geometry().centroid() centroidgeom = centroid.asPoint() # Find the nearest neighbour (index geometries only) nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0] # Check for self join if self.selfjoin and nearestid == infeatureid: # Self join and same feature, so get the two # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( centroidgeom, 2) nearestid = nearestindexes[0] if nearestid == infeatureid and len(nearestindexes) > 1: nearestid = nearestindexes[1] if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestid))) else: nnfeature = next( self.joinvl.getFeatures(QgsFeatureRequest(nearestid))) mindist = inputgeom.distance(nnfeature.geometry()) # Calculate the search rectangle (inputgeom BBOX inpbbox = infeature.geometry().boundingBox() minx = inpbbox.xMinimum() - mindist maxx = inpbbox.xMaximum() + mindist miny = inpbbox.yMinimum() - mindist maxy = inpbbox.yMaximum() + mindist #minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist) #maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist) #miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist) #maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist) searchrectangle = QgsRectangle(minx, miny, maxx, maxy) # Fetch the candidate join geometries closefids = self.joinlind.intersects(searchrectangle) # Loop through the geometries and choose the closest # one for closefid in closefids: if self.abort is True: break # Check for self join and identical feature if self.selfjoin and closefid == infeatureid: continue if self.selectedjoonly: closef = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(closefid))) else: closef = next( self.joinvl.getFeatures( QgsFeatureRequest(closefid))) thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Check all the features of the join layer! mindist = float("inf") # should not be necessary for inFeatJoin in self.joinf: if self.abort is True: break joingeom = inFeatJoin.geometry() thisdistance = inputgeom.distance(joingeom) # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break if not self.abort: # Collect the attribute atMapA = infeature.attributes() atMapB = nnfeature.attributes() attrs = [] attrs.extend(atMapA) attrs.extend(atMapB) attrs.append(mindist) # Create the feature outFeat = QgsFeature() # Use the original input layer geometry!: outFeat.setGeometry(infeature.geometry()) # Use the modified input layer geometry (could be # centroid) #outFeat.setGeometry(inputgeom) # Add the attributes outFeat.setAttributes(attrs) self.calculate_progress() self.features.append(outFeat) #self.mem_joinl.dataProvider().addFeatures([outFeat]) 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('NNJoinEngine', message)
class CatchmentAnalysis(QObject): # Setup signals finished = pyqtSignal(object) error = pyqtSignal(Exception, str) progress = pyqtSignal(float) warning = pyqtSignal(str) def __init__(self, iface, settings): QObject.__init__(self) self.concave_hull = ct.ConcaveHull() self.iface = iface self.settings = settings self.killed = False def analysis(self): if has_pydevd and is_debug: pydevd.settrace('localhost', port=53100, stdoutToServer=True, stderrToServer=True, suspend=False) if self.settings: try: # Prepare the origins origins = self.origin_preparation(self.settings['origins'], self.settings['name']) self.progress.emit(10) if self.killed: return # Build the graph graph, tied_origins = self.graph_builder( self.settings['network'], self.settings['cost'], origins, self.settings['network tolerance'], self.settings['crs'], self.settings['epsg']) self.progress.emit(20) if self.killed: return # Run the analysis catchment_network, catchment_points = self.graph_analysis( graph, tied_origins, self.settings['distances']) self.progress.emit(40) if self.killed: return # Create output signal output = { 'output network features': None, 'output polygon features': None, 'distances': self.settings['distances'] } network = self.settings['network'] # Write and render the catchment polygons if self.settings['output polygon check']: new_fields = QgsFields() new_fields.append(QgsField('id', QVariant.Int)) new_fields.append(QgsField('origin', QVariant.String)) new_fields.append(QgsField('distance', QVariant.Int)) output_polygon_features = self.polygon_writer( catchment_points, self.settings['distances'], new_fields, self.settings['polygon tolerance'], ) output['output polygon features'] = output_polygon_features self.progress.emit(70) if self.killed: return # get fields new_fields = self.get_fields(origins, self.settings['name']) # Write and render the catchment network output_network_features = self.network_writer( catchment_network, new_fields, self.settings['name']) output['output network features'] = output_network_features if self.killed is False: self.progress.emit(100) self.finished.emit(output) except Exception as e: self.error.emit(e, traceback.format_exc()) def origin_preparation(self, origin_vector, origin_name_field): # Create a dictionary of origin point dictionaries containing name and geometry origins = [] # Loop through origin and get or create points for i, f in enumerate(origin_vector.getFeatures()): # Get origin name if origin_name_field: origin_name = f[origin_name_field] else: origin_name = i # "origin_" + "%s" % (i+1) origins.append({ 'name': origin_name, 'geom': f.geometry().centroid() }) return origins def graph_builder(self, network, cost_field, origins, tolerance, crs, epsg): # Settings otf = False # Get index of cost field network_fields = network.fields() network_cost_index = network_fields.indexFromName(cost_field) # Setting up graph build director director = QgsVectorLayerDirector(network, -1, '', '', '', QgsVectorLayerDirector.DirectionBoth) # Determining cost calculation if cost_field != 'length': strategy = ct.CustomCost(network_cost_index, 0.01) else: strategy = QgsNetworkDistanceStrategy() # Creating graph builder director.addStrategy(strategy) builder = QgsGraphBuilder(crs, otf, tolerance, epsg) # Reading origins and making list of coordinates graph_origin_points = [] # Loop through the origin points and add graph vertex indices for index, origin in enumerate(origins): graph_origin_points.append(origins[index]['geom'].asPoint()) # Get origin graph vertex index tied_origin_vertices = director.makeGraph(builder, graph_origin_points) # Build the graph graph = builder.graph() # Create dictionary of origin names and tied origins tied_origins = {} # Combine origin names and tied point vertices for index, tied_origin in enumerate(tied_origin_vertices): tied_origins[index] = { 'name': origins[index]['name'], 'vertex': tied_origin } self.spIndex = QgsSpatialIndex() self.indices = {} self.attributes_dict = {} self.centroids = {} i = 0 for f in network.getFeatures(): if f.geometry().type() == QgsWkbTypes.LineGeometry: if not f.geometry().isMultipart(): self.attributes_dict[f.id()] = f.attributes() polyline = f.geometry().asPolyline() for idx, p in enumerate(polyline[1:]): ml = QgsGeometry.fromPolylineXY([polyline[idx], p]) new_f = QgsFeature() new_f.setGeometry(ml.centroid()) new_f.setAttributes([f.id()]) new_f.setId(i) self.spIndex.addFeature(new_f) self.centroids[i] = f.id() i += 1 else: self.attributes_dict[f.id()] = f.attributes() for pl in f.geometry().asMultiPolyline(): for idx, p in enumerate(pl[1:]): ml = QgsGeometry.fromPolylineXY([pl[idx], p]) new_f = QgsFeature() new_f.setGeometry(ml.centroid()) new_f.setAttributes([f.id()]) new_f.setId(i) self.spIndex.addFeature(new_f) self.centroids[i] = f.id() i += 1 self.network_fields = network_fields return graph, tied_origins def graph_analysis(self, graph, tied_origins, distances): # Settings catchment_threshold = max(distances) # Variables catchment_network = {} catchment_points = {} # Loop through graph and get geometry and write to catchment network for index in range(graph.edgeCount()): inVertexId = graph.edge(index).fromVertex() outVertexId = graph.edge(index).toVertex() inVertexGeom = graph.vertex(inVertexId).point() outVertexGeom = graph.vertex(outVertexId).point() # only include one of the two possible arcs if inVertexId < outVertexId: arcGeom = QgsGeometry.fromPolylineXY( [inVertexGeom, outVertexGeom]) catchment_network[index] = { 'geom': arcGeom, 'start': inVertexId, 'end': outVertexId, 'cost': {} } # Loop through tied origins and write origin names for tied_point, origin in enumerate(tied_origins): origin_name = tied_origins[tied_point]['name'] catchment_points[tied_point] = {'name': origin_name} catchment_points[tied_point].update( {distance: [] for distance in distances}) # Loop through tied origins and write costs and polygon points i = 1 for tied_point, origin in enumerate(tied_origins): self.progress.emit(20 + int(20 * i / len(tied_origins))) origin_name = tied_origins[tied_point]['name'] originVertexId = graph.findVertex( tied_origins[tied_point]['vertex']) # Run dijkstra and get tree and cost (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, originVertexId, 0) # Loop through graph arcs for index in catchment_network.keys(): if self.killed: break # Define the arc properties inVertexId = catchment_network[index]['start'] outVertexId = catchment_network[index]['end'] inVertexCost = cost[inVertexId] outVertexCost = cost[outVertexId] # this is the permissive option gives cost to the arc based on the closest point, # it just needs to be reach by one node arcCost = min(inVertexCost, outVertexCost) # this is the restrictive option, gives cost to the arc based on the furtherst point, # it needs to be entirely within distance # arcCost = max(inVertexCost, outVertexCost) # If arc is the origin set cost to 0 if outVertexId == originVertexId or inVertexId == originVertexId: catchment_network[index]['cost'][origin_name] = 0 # If arc is connected and within the maximum radius set cost elif arcCost <= catchment_threshold and tree[inVertexId] != -1: if origin_name in catchment_network[index]['cost']: if catchment_network[index]['cost'][origin_name] > int( arcCost): catchment_network[index]['cost'][ origin_name] = int(arcCost) else: catchment_network[index]['cost'][origin_name] = int( arcCost) # Add catchment points for each given radius inVertexGeom = graph.vertex(inVertexId).point() outVertexGeom = graph.vertex(outVertexId).point() seg_length = catchment_network[index]['geom'].length( ) # math.sqrt(inVertexGeom.sqrDist(outVertexGeom)) for distance in distances: if self.killed: break # this option includes both nodes as long as arc is within distance # the polygon is the same as the network output # if arcCost <= distance: # catchment_points[tied_point][distance].extend([inVertexGeom, outVertexGeom]) # this option only includes nodes within distance # it does linear interpolation for extra points if inVertexCost <= distance: catchment_points[tied_point][distance].append( inVertexGeom) # add an extra point with linear referencing if outVertexCost > distance: target_dist = distance - inVertexCost midVertexGeom = catchment_network[index][ 'geom'].interpolate(target_dist).asPoint() catchment_points[tied_point][distance].append( midVertexGeom) if outVertexCost <= distance: catchment_points[tied_point][distance].append( outVertexGeom) # add an extra point with linear referencing if inVertexCost > distance: target_dist = distance - outVertexCost midVertexGeom = catchment_network[index][ 'geom'].interpolate(seg_length - target_dist).asPoint() catchment_points[tied_point][distance].append( midVertexGeom) i += 1 return catchment_network, catchment_points def get_fields(self, origins, use_name): # fields: self.network_fields # Setup all unique origin columns and minimum origin distance column # add origin field names if use_name: self.names = set([str(origin['name']) for origin in origins]) for n in self.names: self.network_fields.append(QgsField(n, QVariant.Int)) else: self.names = list(range(0, len(origins))) self.network_fields.append(QgsField('min_dist', QVariant.Int)) return self.network_fields def network_writer(self, catchment_network, new_fields, use_name): # Loop through arcs in catchment network and write geometry and costs i = 0 features = [] for k, v in catchment_network.items(): self.progress.emit(70 + int(30 * i / len(catchment_network))) if self.killed is True: break # Get arc properties arc_geom = v['geom'] arc_cost_dict = { str(key): value for key, value in list(v['cost'].items()) } i += 1 # Ignore arc if not connected or outside of catchment if len(arc_cost_dict) > 0: # Create feature and write id and geom f = QgsFeature() # get original feature attributes centroid_match = self.spIndex.nearestNeighbor( arc_geom.centroid().asPoint(), 1).pop() original_feature_id = self.centroids[centroid_match] f_attrs = self.attributes_dict[original_feature_id] arc_cost_list = [] non_null_arc_cost_list = [] for name in self.names: try: dist = arc_cost_dict[str(name)] arc_cost_list.append(dist) non_null_arc_cost_list.append(dist) except KeyError: arc_cost_list.append(NULL) f.setFields(new_fields) if use_name: f.setAttributes(f_attrs + arc_cost_list + [min(non_null_arc_cost_list)]) else: f.setAttributes(f_attrs + [min(non_null_arc_cost_list)]) f.setGeometry(arc_geom) # Write feature to output network layer features.append(f) i += 1 return features def polygon_writer(self, catchment_points, distances, new_fields, polygon_tolerance): # Setup unique origin dictionary containing all distances unique_origins_list = [] polygon_dict = {} output_polygon_features = [] i = 1 for tied_point in catchment_points: if self.killed: break self.progress.emit(40 + int(30 * i / len(catchment_points))) name = catchment_points[tied_point]['name'] if name not in unique_origins_list: polygon_dict[name] = {distance: [] for distance in distances} unique_origins_list.append(name) # Creating hull for each distance and if applicable in a list for distance in distances: if self.killed: break points = catchment_points[tied_point][distance] if len(points) > 2: # Only three points can create a polygon hull = self.concave_hull.concave_hull( points, polygon_tolerance) polygon_dict[name][distance].append(hull) i += 1 # Generate the polygons index = 1 hull_validity = True for name in polygon_dict: for distance in distances: for hull in polygon_dict[name][ distance]: # Later add combine functionality if self.killed: break # Check if hull is a actual polygon try: polygon_geom = QgsGeometry.fromPolygonXY([ hull, ]) except TypeError: hull_validity = False continue if polygon_geom: p = QgsFeature() p.setFields(new_fields) p.setAttribute('id', index) p.setAttribute('origin', name) p.setAttribute('distance', distance) p.setGeometry(polygon_geom) output_polygon_features.append(p) index += 1 if not hull_validity: self.warning.emit( 'Polygon tolerance too high for small cost bands.') return output_polygon_features def kill(self): self.killed = True
def run(self): # Converter escala textual para numérica (denominador) escala = self.escalaAvaliacao.currentText() # '1:100.000' escalaAval = escala.split(':')[1].replace('.','') escalaAval = int(escalaAval) self.canvas.zoomScale(escalaAval) listaHomologosXY = {} # dicionario yx. listaHomologosZ = {} # dicionario z. # Raio definido raio = self.spinBox.value() # Layers selecionados layer1 = self.referenciaComboBox.currentLayer() layer2 = self.avaliacaoComboBox.currentLayer() # Teste para identificar se esta setado. XY = False Z = False if self.xy.isChecked(): XY = True if self.z.isChecked(): Z = True # Troca de referência CRS para uma que utiliza medição em metros (3857). source1 = layer1.crs() source2 = layer2.crs() dest = QgsCoordinateReferenceSystem(3857) tr1 = QgsCoordinateTransform(source1, dest) tr2 = QgsCoordinateTransform(source2, dest) lista1 = [feat1 for feat1 in layer1.getFeatures()] lista2 = [feat2 for feat2 in layer2.getFeatures()] lista3 = [] spIndex1 = QgsSpatialIndex() neighbour = QgsFeature() for feat1 in lista1: spIndex1.insertFeature(feat1) # transforma features em indices espaciais geom1 = QgsGeometry(feat1.geometry()) geom1.transform(tr1) raioTeste = raio neighbour = None encontrado = False for feat2 in lista2: pt = feat2.geometry().asPoint() nearestid = spIndex1.nearestNeighbor(pt, 2)[0] # realiza a analise do vizinho mais próximo, numero de vizinhos é definido no segundo argumento. #print nearestid request = QgsFeatureRequest().setFilterFid(nearestid) geom2 = QgsGeometry(feat2.geometry()) geom2.transform(tr2) if geom1.buffer (raioTeste, 20 ) .contains (geom2): raioTeste = sqrt (geom1.asPoint (). sqrDist (geom2.asPoint ())) neighbour = feat2 encontrado = True # apenas pra descobrir se não foi encontrado. if encontrado == False: #print u'\nHouve pontos nao encontrados dentro do raio definido.' frase = ("<span style='color:purple'>HOUVE PONTOS NAO ENCONTRADOS.</span>") lista3.append(frase) else: if XY: listaHomologosXY[int(feat1.id()), int(neighbour.id())] = (raioTeste) if Z: listaHomologosZ[int(feat1.id()), int(neighbour.id())] = feat1.geometry().geometry().z() - neighbour.geometry().geometry().z() lista2.remove(neighbour) if XY or Z: #print '\nHomologos: \n', listaHomologosXY.keys() distAcum = 0 resultado2 = "<span style='color:orange'>DISTANCIA ENTRE PONTOS: </span>",[ round(v, 2) for v in listaHomologosXY.values()] resultado1 = "<span style='color:orange'>PONTOS HOMOLOGOS: </span>",listaHomologosXY.keys() #print '\nDistancia entre pontos Homologados:\n',resultado2 lista3.append(resultado1) lista3.append(resultado2) if XY: distAcum = 0 for valorXY in listaHomologosXY.values(): distAcum += valorXY resultado = int(distAcum / len(listaHomologosXY)) #print '\nDistancia media:\n', round(resultado,2) b = "<span style='color:orange'>DISTANCIA MEDIA: </span>", round(resultado,2) lista3.append(b) if Z: zAcum = 0 for valorZ in listaHomologosZ.values(): zAcum += valorZ resultado = int(zAcum / len(listaHomologosZ)) #print '\nDiferenca media de elevacao: \n', round(resultado,3) a = "<span style='color:orange'>DISTANCIA MEDIA DE ELEVACAO: </span>", round(resultado,3) lista3.append(a) # Formatação para apresentar QmessageBox de forma bem distribuida. message = u"" one_data = u"<p>{0}</p>" two_data = u"<p>{0} {1}</p>" for data in lista3: message += one_data.format(data) if type(data) == str else two_data.format(data[0], data[1]) QMessageBox.about(self, "RESULTADO: ", message )
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ # Retrieve the feature source and sink. The 'dest_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. arrets = self.parameterAsSource(parameters, self.STOPS, context) stop_id=self.parameterAsFields(parameters,self.STOP_ID,context)[0] noeuds = self.parameterAsSource(parameters, self.NOEUDS, context) node_id=self.parameterAsFields(parameters,self.NODE_ID,context)[0] mode_i=self.parameterAsString(parameters,self.MODE_ORI,context) mode_j=self.parameterAsString(parameters,self.MODE_DES,context) texte_i=self.parameterAsString(parameters,self.TEXTE_ORI,context) texte_j=self.parameterAsString(parameters,self.TEXTE_DES,context) rayon=self.parameterAsDouble(parameters,self.RAYON,context) vitesse=self.parameterAsDouble(parameters,self.VITESSE,context) nb_max=self.parameterAsInt(parameters,self.MAX_NB,context) # Compute the number of steps to display within the progress bar and # get features from source ##a=fenetre.split(",") ##fenetre2=QgsRectangle(float(a[0]),float(a[2]),float(a[1]),float(a[3])) arr=[a for a in arrets.getFeatures()] nb=len(arr) index=QgsSpatialIndex(noeuds.getFeatures()) champs=QgsFields() champs.append(QgsField('i',QVariant.String,len=15)) champs.append(QgsField('j',QVariant.String,len=15)) champs.append(QgsField(self.tr('length'),QVariant.Double)) champs.append(QgsField(self.tr('time'),QVariant.Double)) champs.append(QgsField(self.tr('mode'),QVariant.String,len=10)) (table_connecteurs,dest_id) = self.parameterAsSink(parameters, self.CONNECTEURS,context,champs, QgsWkbTypes.LineString, noeuds.sourceCrs()) nom_fichier=dest_id fichier_connecteurs=os.path.splitext(nom_fichier)[0]+".txt" sortie=codecs.open(fichier_connecteurs,"w",encoding="utf-8") nbc=0 for i,n in enumerate(arr): near=index.nearestNeighbor(n.geometry().centroid().asPoint(),nb_max) feedback.setProgress(i*100/nb) if len(near)>0: for k,nearest in enumerate(near): if k<nb_max: f=noeuds.getFeatures(request=QgsFeatureRequest(nearest)) for j, g in enumerate(f): if j==0: l=n.geometry().distance(g.geometry()) id_node=unicode(g.attribute(node_id)) id_stop=unicode(n.attribute(stop_id)) if l<rayon: nbc+=1 gline=QgsGeometry.fromPolylineXY([QgsPointXY(n.geometry().centroid().asPoint()),QgsPointXY(g.geometry().centroid().asPoint())]) hline=QgsGeometry.fromPolylineXY([QgsPointXY(g.geometry().centroid().asPoint()),QgsPointXY(n.geometry().centroid().asPoint())]) fline=QgsFeature() fline.setGeometry(gline) ll=gline.length() moda=unicode(mode_i)+unicode(mode_j) if vitesse<=0: fline.setAttributes([id_stop,id_node, ll/1000,0.0,moda]) else: fline.setAttributes([id_stop,id_node, ll/1000,ll*60/(vitesse*1000),moda]) fline2=QgsFeature() fline2.setGeometry(hline) modb=unicode(mode_j)+unicode(mode_i) if vitesse<=0: fline2.setAttributes([id_node,id_stop, ll/1000,0,modb]) else: fline2.setAttributes([id_node,id_stop, ll/1000,ll*60/(vitesse*1000),modb]) table_connecteurs.addFeature(fline) table_connecteurs.addFeature(fline2) if vitesse>0: sortie.write(id_node+';'+id_stop+';'+str((60/vitesse)*(ll/1000.0))+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+modb+';'+modb+'\n') sortie.write(id_stop+';'+id_node+';'+str((60/vitesse)*(ll/1000.0))+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+moda+';'+moda+'\n') else: sortie.write(id_node+';'+id_stop+';'+str(0.0)+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+modb +';'+modb+'\n') sortie.write(id_stop+';'+id_node+';'+str(0.0)+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+moda+';'+moda+'\n') feedback.setProgressText(unicode(nbc)+"/"+unicode(nb)+self.tr(" connected nodes")) sortie.close() 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)) A = source.sourceExtent() A = float(A.width() * A.height()) extent = self.parameterAsExtent(parameters, self.EXTENT, context, source.sourceCrs()) # feedback.pushInfo('Extent: {}'.format(extent)) if extent.area() != 0: A = float(extent.width() * extent.height()) k = self.parameterAsInt(parameters, self.K, context) output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) self.path = output_file spatialIndex = QgsSpatialIndex(source, feedback) count = source.featureCount() features = source.getFeatures() total = 100.0 / count if count else 1 #initialize nnDistances dict nnDistances = {} #key = k , value = list of distances x_values = [] for i in range(0, k): nnDistances[i + 1] = [] x_values.append(i + 1) #compute distances distance = QgsDistanceArea() distance.setSourceCrs(source.sourceCrs(), context.transformContext()) distance.setEllipsoid(context.project().ellipsoid()) for current, feat in enumerate(features): if feedback.isCanceled(): break neighbours = spatialIndex.nearestNeighbor( feat.geometry().asPoint(), k + 1) for i in range(0, k): # request = QgsFeatureRequest().setFilterFid(i).setSubsetOfAttributes([]) # neighbour = next(source.getFeatures(request)) neighbour = QgsFeature() if i + 1 < len(neighbours): # index 0 = point him self ! source.getFeatures(QgsFeatureRequest( neighbours[i + 1])).nextFeature(neighbour) dist = distance.measureLine(neighbour.geometry().asPoint(), feat.geometry().asPoint()) nnDistances[i + 1].append(dist) feedback.setProgress(int(current * total)) results = {} do_values = [] de_values = [] de_max_values = [] de_min_values = [] nni_values = [] #TODO : add edges correction #From Crimestat Manual: If the observed nearest neighbor distance #for point i is equal to or less than the distance #to the nearest borderr, it is retanined. (retain or exclude?) for k, distances in nnDistances.items(): if len(distances) == 0: x_values = x_values[:k - 1] break do = float(sum(distances)) / count if k == 0: de = float(0.5 / math.sqrt(count / A)) else: de = (k * math.factorial(2 * k)) / (math.pow( (math.pow(2, k) * math.factorial(k)), 2) * math.sqrt(count / A)) nni = float(do / de) SE = float(0.26136 / math.sqrt(count**2 / A)) #TODO : correct for k > 1 do_values.append(round(do)) de_values.append(round(de)) de_max_values.append(round(de + SE)) de_min_values.append(round(de - SE)) nni_values.append(round(nni, 3)) result = {} result['K'] = k result['OBSERVED_MD'] = do result['EXPECTED_MD'] = de result['NN_INDEX'] = nni result['POINT_COUNT'] = count if k == 0: result['Z_SCORE'] = float((do - de) / SE) results[k] = result feedback.pushInfo('Results: {}'.format(results)) if not feedback.isCanceled(): do_data = go.Scatter(x=x_values, y=do_values, mode='lines+markers', name=self.tr('Observed mean distance')) de_data = go.Scatter(x=x_values, y=de_values, mode='lines+markers', marker=dict(size=5, color='rgba(120, 120, 120, .8)', line=dict(width=0, color='rgb(0, 0, 0)')), name=self.tr('Expected distance (if random)')) x_rev = x_values[::-1] de_min_values = de_min_values[::-1] se_data = go.Scatter( x=x_values + x_rev, y=de_max_values + de_min_values, fill='tozerox', fillcolor='rgba(120, 120, 120, .3)', line=dict(color='rgba(255,255,255,0)'), showlegend=False, name=self.tr('Expected distance (SE)'), ) nni_data = go.Scatter( x=x_values, y=nni_values, mode='lines+markers', name=self.tr('Observed mean distance'), showlegend=False, ) fig = tools.make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1) fig.append_trace(do_data, 1, 1) fig.append_trace(de_data, 1, 1) fig.append_trace(se_data, 1, 1) fig.append_trace(nni_data, 2, 1) fig['layout'].update( title=self.tr('K-Nearest neighbours'), hovermode='x', xaxis=dict(title=self.tr('K-Order'), ticklen=5, zeroline=False, range=[0.9, len(x_values) + 0.1]), yaxis=dict( title=self.tr('Distance [m]'), ticklen=5, gridwidth=1, ), yaxis2=dict( title=self.tr('NN Index'), ticklen=5, gridwidth=1, range=[ 0, 1 if max(nni_values) < 1 else max(nni_values) + 0.1 ]), showlegend=True, legend=dict(orientation="h", y=1)) # fig = go.Figure(data=[do_data, de_data, se_data], layout=layout) plt.offline.plot(fig, filename=output_file, auto_open=False) self.output = {self.OUTPUT_HTML_FILE: output_file} return self.output
class TracingPipelines(QgsTask): def __init__(self, pipelines, valves, description='TracingCAJ', user_distance=0.001, onfinish=None, debug=False): super().__init__(description, QgsTask.CanCancel) self.onfinish = onfinish self.debug = debug self.__user_distance = user_distance self._pipelines_features = pipelines[0] self._valves_features = valves[0] self.__list_valves = [] self.__list_visited_pipelines = [] self.__list_visited_pipelines_ids = [] self.__q_list_pipelines = [] self.__q_list_pipelines_ids = [] self.__iterations = 0 self.__exception = None # Cria os índices espaciais self.__idx_pipelines = None self.__idx_valves = None if self.__idx_valves is None or self.__idx_pipelines is None: self.__create_spatial_index() def run(self): QgsMessageLog.logMessage(f'Started task {self.description()}', 'TracingCAJ', Qgis.Info) # Busca por redes selecionadas (necessário ser apenas uma) if self.debug: self._pipelines_features.selectByIds([4343]) # self._pipelines_features.getFeatures(16) selected_pipeline = self._pipelines_features.selectedFeatures() if len(selected_pipeline) != 1: QgsMessageLog.logMessage('Selecione apenas UMA rede', 'TracingCAJ', Qgis.Info) return False else: self.__q_list_pipelines.append(selected_pipeline[0].geometry()) self.__q_list_pipelines_ids.append(selected_pipeline[0].id()) while len(self.__q_list_pipelines) > 0: self.__iterations += 1 # check isCanceled() to handle cancellation if self.isCanceled(): return False pipeline = self.__q_list_pipelines.pop(0) pipeline_id = self.__q_list_pipelines_ids.pop(0) if pipeline_id not in self.__list_visited_pipelines_ids: self.__list_visited_pipelines.append(pipeline) self.__list_visited_pipelines_ids.append(pipeline_id) v1 = pipeline.vertexAt(0) if self.debug: v2 = pipeline.vertexAt(pipeline.get()[0].childCount() - 1) else: v2 = pipeline.vertexAt(len(pipeline.get()) - 1) try: self.__find_neighbors(v1) self.__find_neighbors(v2) except Exception as e: self.__exception = e return False return True def finished(self, result): if result: self._valves_features.selectByIds(self.__list_valves) self._pipelines_features.selectByIds( self.__list_visited_pipelines_ids) if self.onfinish: self.onfinish() QgsMessageLog.logMessage( f"Task {self.description()} has been executed correctly" f"Iterations: {self.__iterations}" f"Pipelines: {self.__list_visited_pipelines_ids}" f"Valves: {self.__list_valves}", 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() def __create_spatial_index(self): self.__idx_pipelines = QgsSpatialIndex( self._pipelines_features.getFeatures(), flags=QgsSpatialIndex.FlagStoreFeatureGeometries) self.__idx_valves = QgsSpatialIndex( self._valves_features.getFeatures(), flags=QgsSpatialIndex.FlagStoreFeatureGeometries) def __find_neighbors(self, point_vertex): reg_isvisivel = None reg_status = None # Busca pelo registro mais próximo, dentro do raio maxDistance=user_distance reg_nearest = self.__idx_valves.nearestNeighbor( point=QgsPointXY(point_vertex), neighbors=1, maxDistance=self.__user_distance) if len(reg_nearest) > 0: # visivel = 'sim' = registro visível | visivel = 'não' = registro não visível reg_isvisivel = str( list(self._valves_features.getFeatures(reg_nearest))[0] ['visivel']) # status = 0 = 'Aberto' | status = 1 = 'Fechado' reg_status = str( list(self._valves_features.getFeatures(reg_nearest))[0] ['status']) if len(reg_nearest) > 0: if reg_isvisivel.upper() != 'NÃO' and reg_status == '0': self.__list_valves.append(reg_nearest[0]) else: # Busca pelas 4 redes mais próximas dentro do raio maxDistance=user_distance pipelines_nearest = self.__idx_pipelines.nearestNeighbor( point=QgsPointXY(point_vertex), neighbors=4, maxDistance=self.__user_distance) if len(pipelines_nearest) > 0: for pipeline_id in pipelines_nearest: pipeline_geometry = self.__idx_pipelines.geometry( pipeline_id) if pipeline_id not in self.__list_visited_pipelines_ids: self.__q_list_pipelines_ids.append(pipeline_id) self.__q_list_pipelines.append(pipeline_geometry)
def processAlgorithm(self, parameters, context, feedback): layer = self.parameterAsSource(parameters, self.Centerline, context) layer2 = self.parameterAsVectorLayer(parameters, self.Polygons, context) samples = parameters[self.Samples] distance = parameters[self.Distance] FC = parameters[self.FC] context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) if layer.sourceCrs() != layer2.sourceCrs(): feedback.reportError(QCoreApplication.translate('Error','WARNING: Centerline and Polygon input do not have the same projection')) Precision=5 if FC: field_names = ['Distance','SP_Dist','Width','Deviation','DWidthL','DWidthR'] else: field_names = ['Distance','SP_Dist','Width','Deviation','DWidthL','DWidthR','Diff'] fields = QgsFields() fields.append( QgsField('ID', QVariant.Int )) for name in field_names: fields.append( QgsField(name, QVariant.Double )) (writer, dest_id) = self.parameterAsSink(parameters, self.Output, context, fields, QgsWkbTypes.LineString, layer.sourceCrs()) fet = QgsFeature() field_check =layer.fields().indexFromName('ID') field_check2 =layer2.fields().indexFromName('ID') if field_check == -1 or field_check2 == -1: feedback.reportError(QCoreApplication.translate('Error','Centerline and Polygon input feature require a matching ID field!')) return {} total = 0 counts = {} if FC: vertices = st.run("native:extractvertices", {'INPUT':layer2,'OUTPUT':'memory:'}) index = QgsSpatialIndex(vertices['OUTPUT'].getFeatures()) data = {feature.id():feature for feature in vertices['OUTPUT'].getFeatures()} SPS = {} SPE = {} values = {} values2 = {} feats = {f["ID"]:f for f in layer2.getFeatures()} 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] v3 = counts[ID] + 1 if c > v: SPS[ID] = [(startx,starty),(endx,endy)] values[ID] = c if c < v2: SPE[ID] = [(startx,starty),(endx,endy)] values2[ID] = c counts[ID] = v3 else: SPS[ID] = [(startx,starty),(endx,endy)] values[ID] = c SPE[ID] = [(startx,starty),(endx,endy)] values2[ID] = c counts[ID] = 1 except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) continue ##Possible Collapsed Polyline? del values,values2 total = 100.0/float(total) ID = None feedback.pushInfo(QCoreApplication.translate('Update','Creating Width Measurements')) report = True for enum,feature in enumerate(layer.getFeatures()): try: if total != -1: feedback.setProgress(int(enum*total)) pnt = feature.geometry() L = pnt.length() if pnt.isMultipart(): pnt = pnt.asMultiPolyline()[0] else: pnt = pnt.asPolyline() curID = feature["ID"] if ID != curID: startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision) midx,midy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision) ID = curID if samples > 0: if distance: Counter = L Limit = float(samples) else: Counter = 1 Limit = round((counts[ID]/float(samples)),0) continue endx,endy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision) if samples > 0: if distance: Counter += L else: Counter += 1 if Counter < Limit: startx,starty = midx,midy midx,midy = endx,endy continue if FC: startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision) near = index.nearestNeighbor(QgsPointXY(startx,starty), 1) SPv = 1e12 midx,midy = data[near[0]].geometry().asPoint() dx,dy = startx-midx,starty-midy shortestPath = sqrt((dx**2)+(dy**2)) if shortestPath < SPv: SPv = shortestPath near = index.nearestNeighbor(QgsPointXY(endx,endy), 1) midx,midy = data[near[0]].geometry().asPoint() dx,dy = endx-midx,endy-midy shortestPath = sqrt((dx**2)+(dy**2)) if shortestPath < SPv: SP = shortestPath else: m = ((starty - endy)/(startx - endx)) #Slope inter = feats[curID] Distance = inter.geometry().boundingBox().width()/2 if startx==endx: #if vertical x1,y1 = midx+Distance,midy x2,y2 = midx - Distance,midy else: m = ((starty - endy)/(startx - endx)) #Slope angle = degrees(atan(m)) + 90 m = tan(radians(angle)) #Angle to Slope c,s = (1/sqrt(1+m**2),m/sqrt(1+m**2)) #cosine and sin x1,y1 = (midx + Distance*(c),midy + Distance*(s)) x2,y2 = (midx - Distance*(c),midy - Distance*(s)) geom = QgsGeometry.fromPolylineXY([QgsPointXY(x1,y1),QgsPointXY(midx,midy),QgsPointXY(x2,y2)]) geom = geom.intersection(inter.geometry()) if geom.isMultipart(): polyline = geom.asMultiPolyline() if len(polyline) == 0: startx,starty = midx,midy midx,midy = endx,endy continue for line in polyline: if len(line)==3: t=1 start,mid,end = line geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(start[0],start[1]),QgsPointXY(mid[0],mid[1])]) geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(mid[0],mid[1]),QgsPointXY(end[0],end[1])]) geom = QgsGeometry.fromPolylineXY([QgsPointXY(start[0],start[1]),QgsPointXY(mid[0],mid[1]),QgsPointXY(end[0],end[1])]) break else: try: line = geom.asPolyline() except Exception as e: startx,starty = midx,midy midx,midy = endx,endy if report: report = False feedback.reportError(QCoreApplication.translate('Error','Width measurement along centerline does not intersect with input polygons. Check 1. ID fields corresponds between centerline and polygons 2. Geometry of centerline and polygon inputs by using the "Fix Geometries" tool')) continue geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(line[0][0],line[0][1]),QgsPointXY(line[1][0],line[1][1])]) geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(line[1][0],line[1][1]),QgsPointXY(line[2][0],line[2][1])]) Widths = [geom1.length(),geom2.length()] SP = list(SPS[curID]) SP.extend(list(SPE[curID])) D = 0 for start,end in combinations(SP,2): dx = start[0] - end[0] dy = start[1] - end[1] shortestPath = sqrt((dx**2)+(dy**2)) if shortestPath > D: D = shortestPath s = QgsPointXY(start[0],start[1]) e = QgsPointXY(end[0],end[1]) m = s.sqrDist(e) u = ((midx - s.x()) * (e.x() - s.x()) + (midy - s.y()) * (e.y() - s.y()))/(m) x = s.x() + u * (e.x() - s.x()) y = s.y() + u * (e.y() - s.y()) d = ((e.x()-s.x())*(midy-s.y()) - (e.y() - s.y())*(midx - s.x())) #Determine which side of the SP the symmetry occurs dx = s.x() - e.x() dy = s.y() - e.y() shortestPath = sqrt((dx**2)+(dy**2)) dx = s.x() - x dy = s.y() - y shortestPath1 = sqrt((dx**2)+(dy**2)) if shortestPath < shortestPath1: sym = QgsGeometry.fromPolylineXY([QgsPointXY(e.x(),e.y()),QgsPointXY(midx,midy)]) else: sym = QgsGeometry.fromPolylineXY([QgsPointXY(x,y),QgsPointXY(midx,midy)]) if d < 0: DW = -(sym.length()) else: DW = sym.length() if FC: W = SPv*2 rows = [curID,feature['Distance'],feature['SP_Dist'],W,DW,(W/2)+DW,-(W/2)+DW] geom = feature.geometry() else: W = geom.length() rows = [curID,feature['Distance'],feature['SP_Dist'],W,DW,(W/2)+DW,-(W/2)+DW,(min(Widths)/max(Widths))*100] startx,starty = midx,midy midx,midy = endx,endy fet.setGeometry(geom) fet.setAttributes(rows) writer.addFeature(fet) if distance: Counter -= samples else: Counter = 0 except Exception as e: #feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) startx,starty = midx,midy midx,midy = endx,endy continue del writer if FC: del data del SPS,SPE 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 generate_od_routes( network_layer: QgsVectorLayer, origin_layer: QgsVectorLayer, poi_layer: QgsVectorLayer, size_field: str, class_field: str, work_layer: QgsVectorLayer = None, work_size_field: str = None, school_layer: QgsVectorLayer = None, school_size_field: str = None, origin_weight_field: str = None, socio_data=None, health_data=None, diversity_data=None, join_on: str = None, max_distance: int = 25000, return_layer: bool = True, return_raw: bool = False, feedback: QgsProcessingFeedback = None, ) -> QgsVectorLayer: """ Shortest path algorithm based on Dijkstra's algorithm :param network_layer: road network :param points_layer: combined from to points :param relations_data: tabular from to id data :param origin_field: name of from field :param destination_field: name of to field :param max_distance: maximum distance/cost :param crs: output layer crs """ if not network_layer.wkbType() & QgsWkbTypes.LineString: raise Exception('Network layer must be of type LineString') crs = network_layer.crs() ## prepare graph director = QgsVectorLayerDirector( source=network_layer, directionFieldId=-1, directDirectionValue='', reverseDirectionValue='', bothDirectionValue='', defaultDirection=QgsVectorLayerDirector.DirectionBoth, ) # First strategy is for actual shortest distance calculation director.addStrategy(QgsNetworkDistanceStrategy()) # 0 # Second strategy is a hack to be able to recover the edge id director.addStrategy(SaveFidStrategy()) # 1 builder = QgsGraphBuilder(crs) ## spatial index poi_sidx = QgsSpatialIndex(poi_layer) work_sidx = QgsSpatialIndex(work_layer) school_sidx = QgsSpatialIndex(school_layer) ## prepare points orig_n = len(origin_layer) poi_n = len(poi_layer) work_n = len(work_layer) if work_layer else 0 school_n = len(school_layer) if school_layer else 0 dest_n = poi_n + work_n + school_n orig_points = [None] * orig_n orig_sizes = np.zeros(orig_n, dtype=float) if socio_data: orig_socio = np.zeros(orig_n, dtype=float) dest_points = [None] * dest_n dest_sizes = [None] * dest_n dest_fids = [None] * dest_n dest_cats = [None] * dest_n orig_id_field = 'deso' for i, feat in enumerate(origin_layer.getFeatures()): orig_points[i] = feat.geometry().asPoint() orig_sizes[i] = feat[size_field] * ( feat[origin_weight_field] if origin_weight_field else 1 ) if socio_data: orig_socio[i] = socio_data[ feat[orig_id_field] ] # FIXME: check if all origins have data if socio_data: orig_socio = orig_socio / np.mean(orig_socio) orig_sizes *= orig_socio for i, feat in enumerate(poi_layer.getFeatures()): dest_points[i] = feat.geometry().asPoint() dest_fids[i] = feat.id() dest_sizes[i] = 1 # TODO: dest size dest_cats[i] = poi_class_map.get(feat[class_field]) if work_layer: for i, feat in enumerate(work_layer.getFeatures(), start=poi_n): dest_points[i] = feat.geometry().asPoint() dest_fids[i] = feat.id() dest_sizes[i] = feat[work_size_field] # TODO: dest size dest_cats[i] = 'work' if school_layer: for i, feat in enumerate(school_layer.getFeatures(), start=(poi_n + work_n)): dest_points[i] = feat.geometry().asPoint() dest_fids[i] = feat.id() dest_sizes[i] = feat[school_size_field] # TODO: dest size dest_cats[i] = 'school' # points = [origin.point for origin in origins_data] + [ # dest.point for dest in dests_data # ] if feedback is None: feedback = QgsProcessingFeedback() def progress(p): if int(10 * p % 100) == 0: print(f'{int(p):#3d}%') feedback.progressChanged.connect(progress) with timing('build network graph'): tied_points = director.makeGraph( builder, orig_points + dest_points, feedback=feedback ) graph = builder.graph() orig_tied_points = tied_points[:orig_n] dest_tied_points = tied_points[orig_n:] poi_tied_points = dest_tied_points[:poi_n] work_tied_points = dest_tied_points[poi_n : poi_n + work_n] school_tied_points = dest_tied_points[poi_n + work_n :] dest_fid_to_tied_points = dict(zip(dest_fids, enumerate(dest_tied_points))) poi_fid_to_tied_points = dict(zip(dest_fids[:poi_n], enumerate(poi_tied_points))) work_fid_to_tied_points = dict( zip(dest_fids[poi_n : poi_n + work_n], enumerate(work_tied_points, start=poi_n)) ) school_fid_to_tied_points = dict( zip( dest_fids[poi_n + work_n :], enumerate(school_tied_points, start=poi_n + work_n), ) ) orig_dests = [None] * orig_n for i, point in enumerate(orig_points): orig_dests[i] = ( [ poi_fid_to_tied_points[fid] for fid in poi_sidx.nearestNeighbor( point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance ) ] + [ work_fid_to_tied_points[fid] for fid in work_sidx.nearestNeighbor( point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance ) ] + [ school_fid_to_tied_points[fid] for fid in school_sidx.nearestNeighbor( point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance ) ] ) step = 100.0 / orig_n time_dijkstra = 0.0 time_find = 0.0 time_route = 0.0 with timing('calculate connecting routes'): routes = [] # for i, (origin_fid, dest_fids) in enumerate(od_data): for i, (orig_point, dests) in enumerate(zip(orig_tied_points, orig_dests)): origin_vertex_id = graph.findVertex(orig_point) # Calculate the tree and cost using the distance strategy (#0) ts = time() (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, origin_vertex_id, 0) time_dijkstra += time() - ts for j, dest_point in dests: if feedback.isCanceled(): return if dest_sizes[j] <= 0: continue category = dest_cats[j] if category is None: continue ts = time() dest_vertex_id = graph.findVertex(dest_point) time_find += time() - ts if tree[dest_vertex_id] != -1 and ( cost[dest_vertex_id] <= MAX_DISTANCE_M or MAX_DISTANCE_M <= 0 # TODO: enable skipping max distance ): route_distance = cost[dest_vertex_id] # route_points = [graph.vertex(dest_vertex_id).point()] cur_vertex_id = dest_vertex_id route_fids = [] # Iterate the graph from dest to origin saving the edges ts = time() while cur_vertex_id != origin_vertex_id: cur_edge = graph.edge(tree[cur_vertex_id]) # Here we recover the edge id through strategy #1 route_fids.append(cur_edge.cost(1)) cur_vertex_id = cur_edge.fromVertex() # route_points.append(graph.vertex(cur_vertex_id).point()) time_route += time() - ts # route_points.reverse() # route_geom = QgsGeometry.fromPolylineXY(route_points)) # Hack to remove duplicate fids route_fids = list( dict.fromkeys(route_fids) ) # NOTE: requires python >= 3.7 for ordered dict FIXME: add python version check route_fids.reverse() # Calc # TODO: Move to matrix and vectorize calculation using numpy gravity_value = poi_gravity_values[category] bike_params = mode_params_bike[category] ebike_params = mode_params_ebike[category] # NOTE: we include dest size in decay here decay = dest_sizes[j] * math.exp( gravity_value * route_distance / 1000.0 ) p_bike = sigmoid(*bike_params, route_distance) p_ebike = sigmoid(*ebike_params, route_distance) # TODO: use namedtuple or dataclass routes.append( Route( i, j, category, route_distance, decay, p_bike, p_ebike, route_fids, ) ) feedback.setProgress(i * step) print(f'dijkstra took: {time_dijkstra:#1.2f} sec') print(f'find vertex took: {time_find:#1.2f} sec') print(f'route took: {time_route:#1.2f} sec') with timing('post process routes'): alpha_bike = 0.8 alpha_ebke = 0.2 decay_sums = {cat: defaultdict(float) for cat in poi_categories} bike_values = {cat: defaultdict(float) for cat in poi_categories} ebike_values = {cat: defaultdict(float) for cat in poi_categories} for route in routes: # NOTE: dest size is included in decay decay_sums[route.cat][route.i] += route.decay for route in routes: decay_sum = decay_sums[route.cat][route.i] # TODO: add T_p and alpha_m T_p = trip_generation[route.cat] bike_value = ( T_p * alpha_bike * orig_sizes[route.i] * route.p_bike * route.decay / decay_sum ) ebike_value = ( T_p * alpha_ebke * orig_sizes[route.i] * route.p_ebike * route.decay / decay_sum ) for fid in route.net_fids: bike_values[route.cat][fid] += float(bike_value) ebike_values[route.cat][fid] += float(ebike_value) # FIXME: Un-kludge this with timing('create result features'): fields = get_fields() segments = [] for feature in network_layer.getFeatures(): fid = feature.id() segment = QgsFeature(fields) segment.setGeometry(QgsGeometry(feature.geometry())) segment['network_fid'] = fid flow = 0.0 for cat in poi_categories: bike_field = f'{cat}_bike_value' ebike_field = f'{cat}_ebike_value' flow_bike = segment[bike_field] = bike_values[cat].get(fid) flow_ebke = segment[ebike_field] = ebike_values[cat].get(fid) if flow_bike is not None: flow += flow_bike if flow_ebke is not None: flow += flow_ebke segment['flow'] = flow segment['lts'] = feature['lts'] segment['vgu'] = feature['vgu'] segment['R'] = flow * feature['ratio'] segments.append(segment) if not return_layer: if return_raw: return segments, bike_values, ebike_values return segments with timing('create result layer'): output_layer = QgsVectorLayer( f'LineString?crs={crs.toWkt()}', 'segments', 'memory' ) with edit(output_layer): for field in fields: output_layer.addAttribute(field) output_layer.addFeatures(segments, flags=QgsFeatureSink.FastInsert) return output_layer
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): # 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 spaced(bar,buildings_layer_path,receiver_points_layer_path,spaced_pts_distance): distance_from_facades = 0.1 buildings_layer_name = os.path.splitext(os.path.basename(buildings_layer_path))[0] buildings_layer = QgsVectorLayer(buildings_layer_path,buildings_layer_name,"ogr") # cp building layer to delete all fields buildings_memory_layer = QgsVectorLayer("Polygon?crs=" + str(buildings_layer.crs().authid()), "polygon_memory_layer", "memory") buildings_memory_layer.dataProvider().addAttributes([]) buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_feat_list = [] for buildings_feat in buildings_feat_all: buildings_feat_list.append(buildings_feat) buildings_memory_layer.dataProvider().addFeatures(buildings_feat_list) buildings_memory_layer.updateExtents() # this is crazy: I had to addd this line otherwise the first processing doesn't work... QgsProject.instance().addMapLayers([buildings_memory_layer]) bar.setValue(1) # this processing alg has as output['OUTPUT'] the layer output = processing.run("native:buffer", {'INPUT': buildings_memory_layer, 'DISTANCE': distance_from_facades, 'DISSOLVE': False, 'OUTPUT': 'memory:'}) # I can now remove the layer from map... QgsProject.instance().removeMapLayers( [buildings_memory_layer.id()] ) bar.setValue(25) # this processing alg has as output['OUTPUT'] the layer output = processing.run("qgis:polygonstolines", {'INPUT': output['OUTPUT'], 'OUTPUT': 'memory:'}) bar.setValue(50) # this processing alg has as output['output'] the layer path... poly_to_lines = output['OUTPUT'] output = processing.run("qgis:pointsalonglines", {'INPUT': poly_to_lines, 'DISTANCE': spaced_pts_distance, 'START_OFFSET': 0, 'END_OFFSET': 0, 'OUTPUT': 'memory:'}) bar.setValue(75) receiver_points_memory_layer = output['OUTPUT'] del output ## Delete pts in buildings # creates SpatialIndex buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_spIndex = QgsSpatialIndex() buildings_feat_all_dict = {} for buildings_feat in buildings_feat_all: buildings_spIndex.insertFeature(buildings_feat) buildings_feat_all_dict[buildings_feat.id()] = buildings_feat receiver_points_memory_layer_all = receiver_points_memory_layer.dataProvider().getFeatures() receiver_points_layer_fields = QgsFields() receiver_points_layer_fields.append(QgsField("id_pt", QVariant.Int)) receiver_points_layer_fields.append(QgsField("id_bui", QVariant.Int)) receiver_points_layer_writer = QgsVectorFileWriter(receiver_points_layer_path, "System", receiver_points_layer_fields, QgsWkbTypes.Point, buildings_layer.crs(), "ESRI Shapefile") receiver_points_feat_id = 0 receiver_memory_feat_total = receiver_points_memory_layer.dataProvider().featureCount() receiver_memory_feat_number = 0 for receiver_memory_feat in receiver_points_memory_layer_all: receiver_memory_feat_number = receiver_memory_feat_number + 1 barValue = receiver_memory_feat_number/float(receiver_memory_feat_total)*25 + 75 bar.setValue(barValue) rect = QgsRectangle() rect.setXMinimum(receiver_memory_feat.geometry().asPoint().x() - distance_from_facades) rect.setXMaximum(receiver_memory_feat.geometry().asPoint().x() + distance_from_facades) rect.setYMinimum(receiver_memory_feat.geometry().asPoint().y() - distance_from_facades) rect.setYMaximum(receiver_memory_feat.geometry().asPoint().y() + distance_from_facades) buildings_selection = buildings_spIndex.intersects(rect) to_add = True receiver_geom = receiver_memory_feat.geometry() building_id_correct = None for buildings_id in buildings_selection: building_geom = buildings_feat_all_dict[buildings_id].geometry() intersectBuilding = QgsGeometry.intersects(receiver_geom, building_geom) building_id_correct = buildings_id if intersectBuilding: to_add = False building_id_correct = None break # picking the nearest building to the receiver point analysed nearestIds = buildings_spIndex.nearestNeighbor(receiver_geom.asPoint(), 1) building_fid = [] for featureId in nearestIds: request = QgsFeatureRequest().setFilterFid(featureId) for feature in buildings_layer.getFeatures(request): dist = receiver_geom.distance(feature.geometry()) building_fid.append((dist, feature.id())) building_fid_correct = min(building_fid, key=lambda x: x[0])[-1] if to_add: attributes = [receiver_points_feat_id, building_fid_correct] fet = QgsFeature() fet.setGeometry(receiver_memory_feat.geometry()) fet.setAttributes(attributes) receiver_points_layer_writer.addFeature(fet) receiver_points_feat_id = receiver_points_feat_id + 1 del receiver_points_layer_writer receiver_points_layer_name = os.path.splitext(os.path.basename(receiver_points_layer_path))[0] receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr") QgsProject.instance().addMapLayers([receiver_points_layer]) QgsProject.instance().reloadAllLayers()
def spaced(bar, buildings_layer_path, receiver_points_layer_path, spaced_pts_distance): distance_from_facades = 0.1 buildings_layer_name = os.path.splitext( os.path.basename(buildings_layer_path))[0] buildings_layer = QgsVectorLayer(buildings_layer_path, buildings_layer_name, "ogr") # cp building layer to delete all fields buildings_memory_layer = QgsVectorLayer( "Polygon?crs=" + str(buildings_layer.crs().authid()), "polygon_memory_layer", "memory") buildings_memory_layer.dataProvider().addAttributes([]) buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_feat_list = [] for buildings_feat in buildings_feat_all: buildings_feat_list.append(buildings_feat) buildings_memory_layer.dataProvider().addFeatures(buildings_feat_list) buildings_memory_layer.updateExtents() # this is crazy: I had to addd this line otherwise the first processing doesn't work... QgsProject.instance().addMapLayers([buildings_memory_layer]) bar.setValue(1) # this processing alg has as output['OUTPUT'] the layer output = processing.run( "native:buffer", { 'INPUT': buildings_memory_layer, 'DISTANCE': distance_from_facades, 'DISSOLVE': False, 'OUTPUT': 'memory:' }) # I can now remove the layer from map... QgsProject.instance().removeMapLayers([buildings_memory_layer.id()]) bar.setValue(25) # this processing alg has as output['OUTPUT'] the layer output = processing.run("qgis:polygonstolines", { 'INPUT': output['OUTPUT'], 'OUTPUT': 'memory:' }) bar.setValue(50) # this processing alg has as output['output'] the layer path... poly_to_lines = output['OUTPUT'] output = processing.run( "qgis:pointsalonglines", { 'INPUT': poly_to_lines, 'DISTANCE': spaced_pts_distance, 'START_OFFSET': 0, 'END_OFFSET': 0, 'OUTPUT': 'memory:' }) bar.setValue(75) receiver_points_memory_layer = output['OUTPUT'] del output ## Delete pts in buildings # creates SpatialIndex buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_spIndex = QgsSpatialIndex() buildings_feat_all_dict = {} for buildings_feat in buildings_feat_all: buildings_spIndex.insertFeature(buildings_feat) buildings_feat_all_dict[buildings_feat.id()] = buildings_feat receiver_points_memory_layer_all = receiver_points_memory_layer.dataProvider( ).getFeatures() receiver_points_layer_fields = QgsFields() receiver_points_layer_fields.append(QgsField("id_pt", QVariant.Int)) receiver_points_layer_fields.append(QgsField("id_bui", QVariant.Int)) receiver_points_layer_writer = QgsVectorFileWriter( receiver_points_layer_path, "System", receiver_points_layer_fields, QgsWkbTypes.Point, buildings_layer.crs(), "ESRI Shapefile") receiver_points_feat_id = 0 receiver_memory_feat_total = receiver_points_memory_layer.dataProvider( ).featureCount() receiver_memory_feat_number = 0 for receiver_memory_feat in receiver_points_memory_layer_all: receiver_memory_feat_number = receiver_memory_feat_number + 1 barValue = receiver_memory_feat_number / float( receiver_memory_feat_total) * 25 + 75 bar.setValue(barValue) rect = QgsRectangle() rect.setXMinimum(receiver_memory_feat.geometry().asPoint().x() - distance_from_facades) rect.setXMaximum(receiver_memory_feat.geometry().asPoint().x() + distance_from_facades) rect.setYMinimum(receiver_memory_feat.geometry().asPoint().y() - distance_from_facades) rect.setYMaximum(receiver_memory_feat.geometry().asPoint().y() + distance_from_facades) buildings_selection = buildings_spIndex.intersects(rect) to_add = True receiver_geom = receiver_memory_feat.geometry() building_id_correct = None for buildings_id in buildings_selection: building_geom = buildings_feat_all_dict[buildings_id].geometry() intersectBuilding = QgsGeometry.intersects(receiver_geom, building_geom) building_id_correct = buildings_id if intersectBuilding: to_add = False building_id_correct = None break # picking the nearest building to the receiver point analysed nearestIds = buildings_spIndex.nearestNeighbor(receiver_geom.asPoint(), 1) building_fid = [] for featureId in nearestIds: request = QgsFeatureRequest().setFilterFid(featureId) for feature in buildings_layer.getFeatures(request): dist = receiver_geom.distance(feature.geometry()) building_fid.append((dist, feature.id())) building_fid_correct = min(building_fid, key=lambda x: x[0])[-1] if to_add: attributes = [receiver_points_feat_id, building_fid_correct] fet = QgsFeature() fet.setGeometry(receiver_memory_feat.geometry()) fet.setAttributes(attributes) receiver_points_layer_writer.addFeature(fet) receiver_points_feat_id = receiver_points_feat_id + 1 del receiver_points_layer_writer receiver_points_layer_name = os.path.splitext( os.path.basename(receiver_points_layer_path))[0] receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr") QgsProject.instance().addMapLayers([receiver_points_layer]) QgsProject.instance().reloadAllLayers()
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 Worker(QtCore.QObject): '''The worker that does the heavy lifting. /* QGIS offers spatial indexes to make spatial search more * effective. QgsSpatialIndex will find the nearest index * (approximate) geometry (rectangle) for a supplied point. * QgsSpatialIndex will only give correct results when searching * for the nearest neighbour of a point in a point data set. * So something has to be done for non-point data sets * * Non-point join data set: * A two pass search is performed. First the index is used to * find the nearest index geometry (approximation - rectangle), * and then compute the distance to the actual indexed geometry. * A rectangle is constructed from this (maximum minimum) * distance, and this rectangle is used to find all features in * the join data set that may be the closest feature to the given * point. * For all the features is this candidate set, the actual * distance to the given point is calculated, and the nearest * feature is returned. * * Non-point input data set: * First the centroid of the non-point input geometry is * calculated. Then the index is used to find the nearest * neighbour to this point (using the approximate index * geometry). * The distance vector to this feature, combined with the * bounding rectangle of the input feature is used to create a * search rectangle to find the candidate join geometries. * For all the features is this candidate set, the actual * distance to the given feature is calculated, and the nearest * feature is returned. * * Joins involving multi-geometry datasets are not supported * by a spatial index. * */ ''' # Define the signals used to communicate back to the application progress = QtCore.pyqtSignal(float) # For reporting progress status = QtCore.pyqtSignal(str) # For reporting status error = QtCore.pyqtSignal(str) # For reporting errors # Signal for sending over the result: finished = QtCore.pyqtSignal(bool, object) def __init__(self, inputvectorlayer, joinvectorlayer, outputlayername, joinprefix, distancefieldname="distance", approximateinputgeom=False, usejoinlayerapproximation=False, usejoinlayerindex=True, selectedinputonly=True, selectedjoinonly=True, excludecontaining=True): """Initialise. Arguments: inputvectorlayer -- (QgsVectorLayer) The base vector layer for the join joinvectorlayer -- (QgsVectorLayer) the join layer outputlayername -- (string) the name of the output memory layer joinprefix -- (string) the prefix to use for the join layer attributes in the output layer distancefieldname -- name of the (new) field where neighbour distance is stored approximateinputgeom -- (boolean) should the input geometry be approximated? Is only be set for non-single-point layers usejoinlayerindexapproximation -- (boolean) should the index geometry approximations be used for the join? usejoinlayerindex -- (boolean) should an index for the join layer be used. selectedinputonly -- Only selected features from the input layer selectedjoinonly -- Only selected features from the join layer excludecontaining -- exclude the containing polygon for points """ QtCore.QObject.__init__(self) # Essential! # Set a variable to control the use of indexes and exact # geometries for non-point input geometries self.nonpointexactindex = usejoinlayerindex # Creating instance variables from the parameters self.inpvl = inputvectorlayer self.joinvl = joinvectorlayer self.outputlayername = outputlayername self.joinprefix = joinprefix self.approximateinputgeom = approximateinputgeom self.usejoinlayerapprox = usejoinlayerapproximation self.selectedinonly = selectedinputonly self.selectedjoonly = selectedjoinonly self.excludecontaining = excludecontaining # Check if the layers are the same (self join) self.selfjoin = False if self.inpvl is self.joinvl: # This is a self join self.selfjoin = True # The name of the attribute for the calculated distance self.distancename = distancefieldname # Creating instance variables for the progress bar ++ # Number of elements that have been processed - updated by # calculate_progress self.processed = 0 # Current percentage of progress - updated by # calculate_progress self.percentage = 0 # Flag set by kill(), checked in the loop self.abort = False # Number of features in the input layer - used by # calculate_progress (set when needed) self.feature_count = 1 # The number of elements that is needed to increment the # progressbar (set when needed) self.increment = 0 def run(self): try: # Check if the layers look OK if self.inpvl is None or self.joinvl is None: self.status.emit('Layer is missing!') self.finished.emit(False, None) return # Check if there are features in the layers incount = 0 if self.selectedinonly: incount = self.inpvl.selectedFeatureCount() else: incount = self.inpvl.featureCount() if incount == 0: self.status.emit('Input layer has no features!') self.finished.emit(False, None) return joincount = 0 if self.selectedjoonly: joincount = self.joinvl.selectedFeatureCount() else: joincount = self.joinvl.featureCount() if joincount == 0: self.status.emit('Join layer has no features!') self.finished.emit(False, None) return # Get the wkbtype of the layers self.inpWkbType = self.inpvl.wkbType() self.joinWkbType = self.joinvl.wkbType() # Check if the input layer does not have geometries if (self.inpvl.geometryType() == QgsWkbTypes.NullGeometry): self.status.emit('No geometries in the input layer!') self.finished.emit(False, None) return # Check if the join layer does not have geometries if (self.joinvl.geometryType() == QgsWkbTypes.NullGeometry): self.status.emit('No geometries in the join layer!') self.finished.emit(False, None) return # Set the geometry type and prepare the output layer inpWkbTypetext = QgsWkbTypes.displayString(int(self.inpWkbType)) # self.inputmulti = QgsWkbTypes.isMultiType(self.inpWkbType) # self.status.emit('wkbtype: ' + inpWkbTypetext) # geometryType = self.inpvl.geometryType() # geometrytypetext = 'Point' # if geometryType == QgsWkbTypes.PointGeometry: # geometrytypetext = 'Point' # elif geometryType == QgsWkbTypes.LineGeometry: # geometrytypetext = 'LineString' # elif geometryType == QgsWkbTypes.PolygonGeometry: # geometrytypetext = 'Polygon' # if self.inputmulti: # geometrytypetext = 'Multi' + geometrytypetext # geomttext = geometrytypetext geomttext = inpWkbTypetext # Set the coordinate reference system to the input # layer's CRS using authid (proj4 may be more robust) if self.inpvl.crs() is not None: geomttext = (geomttext + "?crs=" + str(self.inpvl.crs().authid())) # Retrieve the fields from the input layer outfields = self.inpvl.fields().toList() # Retrieve the fields from the join layer if self.joinvl.fields() is not None: jfields = self.joinvl.fields().toList() for joinfield in jfields: outfields.append(QgsField(self.joinprefix + str(joinfield.name()), joinfield.type())) else: self.status.emit('Unable to get any join layer fields') # Add the nearest neighbour distance field # Check if there is already a "distance" field # (should be avoided in the user interface) # Try a new name if there is a collission collission = True trynumber = 1 distnameorg = self.distancename while collission: # Iterate until there are no collissions collission = False for field in outfields: # This check should not be necessary - handled in the UI if field.name() == self.distancename: self.status.emit( 'Distance field already exists - renaming!') # self.abort = True # self.finished.emit(False, None) # break collission = True self.distancename = distnameorg + str(trynumber) trynumber = trynumber + 1 outfields.append(QgsField(self.distancename, QVariant.Double)) # Create a memory layer using a CRS description self.mem_joinl = QgsVectorLayer(geomttext, self.outputlayername, "memory") # Set the CRS to the inputlayer's CRS self.mem_joinl.setCrs(self.inpvl.crs()) self.mem_joinl.startEditing() # Add the fields for field in outfields: self.mem_joinl.dataProvider().addAttributes([field]) # For an index to be used, the input layer has to be a # point layer, or the input layer geometries have to be # approximated to centroids, or the user has to have # accepted that a join layer index is used (for # non-point input layers). # (Could be extended to multipoint) if (self.inpWkbType == QgsWkbTypes.Point or self.inpWkbType == QgsWkbTypes.Point25D or self.approximateinputgeom or self.nonpointexactindex): # Number of features in the join layer - used by # calculate_progress for the index creation if self.selectedjoonly: self.feature_count = self.joinvl.selectedFeatureCount() else: self.feature_count = self.joinvl.featureCount() # Create a spatial index to speed up joining self.status.emit('Creating join layer index...') # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 self.joinlind = QgsSpatialIndex() # Include geometries to enable exact distance calculations # self.joinlind = QgsSpatialIndex(flags=[QgsSpatialIndex.FlagStoreFeatureGeometries]) if self.selectedjoonly: for feat in self.joinvl.getSelectedFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.calculate_progress() else: for feat in self.joinvl.getFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.calculate_progress() self.status.emit('Join layer index created!') self.processed = 0 self.percentage = 0 # self.calculate_progress() # Is the join layer a multi-geometry layer? # self.joinmulti = QgsWkbTypes.isMultiType(self.joinWkbType) # Does the join layer contain multi geometries? # Try to check the first feature # This is not used for anything yet self.joinmulti = False if self.selectedjoonly: feats = self.joinvl.getSelectedFeatures() else: feats = self.joinvl.getFeatures() if feats is not None: testfeature = next(feats) feats.rewind() feats.close() if testfeature is not None: if testfeature.hasGeometry(): if testfeature.geometry().isMultipart(): self.joinmulti = True # Prepare for the join by fetching the layers into memory # Add the input features to a list self.inputf = [] if self.selectedinonly: for f in self.inpvl.getSelectedFeatures(): self.inputf.append(f) else: for f in self.inpvl.getFeatures(): self.inputf.append(f) # Add the join features to a list (used in the join) self.joinf = [] if self.selectedjoonly: for f in self.joinvl.getSelectedFeatures(): self.joinf.append(f) else: for f in self.joinvl.getFeatures(): self.joinf.append(f) # Initialise the global variable that will contain the # result of the nearest neighbour spatial join (list of # features) self.features = [] # Do the join! # Number of features in the input layer - used by # calculate_progress for the join operation if self.selectedinonly: self.feature_count = self.inpvl.selectedFeatureCount() else: self.feature_count = self.inpvl.featureCount() # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 # Using the original features from the input layer for feat in self.inputf: # Allow user abort if self.abort is True: break self.do_indexjoin(feat) self.calculate_progress() self.mem_joinl.dataProvider().addFeatures(self.features) self.status.emit('Join finished') except: import traceback self.error.emit(traceback.format_exc()) self.finished.emit(False, None) if self.mem_joinl is not None: self.mem_joinl.rollBack() else: self.mem_joinl.commitChanges() if self.abort: self.finished.emit(False, None) else: self.status.emit('Delivering the memory layer...') self.finished.emit(True, self.mem_joinl) def calculate_progress(self): '''Update progress and emit a signal with the percentage''' self.processed = self.processed + 1 # update the progress bar at certain increments if (self.increment == 0 or self.processed % self.increment == 0): # Calculate percentage as integer perc_new = (self.processed * 100) / self.feature_count if perc_new > self.percentage: self.percentage = perc_new self.progress.emit(self.percentage) def kill(self): '''Kill the thread by setting the abort flag''' self.abort = True def do_indexjoin(self, feat): '''Find the nearest neigbour of a feature. Using an index, if possible Parameter: feat -- The feature for which a neighbour is sought ''' infeature = feat # Get the feature ID infeatureid = infeature.id() # self.status.emit('**infeatureid: ' + str(infeatureid)) # Get the feature geometry inputgeom = infeature.geometry() # Check for missing input geometry if inputgeom.isEmpty(): # Prepare the result feature atMapA = infeature.attributes() atMapB = [] for thefield in self.joinvl.fields(): atMapB.extend([None]) attrs = [] attrs.extend(atMapA) attrs.extend(atMapB) attrs.append(0 - float("inf")) # Create the feature outFeat = QgsFeature() # Use the original input layer geometry!: outFeat.setGeometry(infeature.geometry()) # Use the modified input layer geometry (could be # centroid) # outFeat.setGeometry(inputgeom) # Add the attributes outFeat.setAttributes(attrs) # self.calculate_progress() self.features.append(outFeat) # self.mem_joinl.dataProvider().addFeatures([outFeat]) self.status.emit("Warning: Input feature with " "missing geometry: " + str(infeature.id())) return # Shall approximate input geometries be used? if self.approximateinputgeom: # Use the centroid as the input geometry inputgeom = infeature.geometry().centroid() # Check if the coordinate systems are equal, if not, # transform the input feature! if (self.inpvl.crs() != self.joinvl.crs()): try: # inputgeom.transform(QgsCoordinateTransform( # self.inpvl.crs(), self.joinvl.crs(), None)) # transcontext = QgsCoordinateTransformContext() # inputgeom.transform(QgsCoordinateTransform( # self.inpvl.crs(), self.joinvl.crs(), transcontext)) inputgeom.transform(QgsCoordinateTransform( self.inpvl.crs(), self.joinvl.crs(), QgsProject.instance())) except: import traceback self.error.emit(self.tr('CRS Transformation error!') + ' - ' + traceback.format_exc()) self.abort = True return # Find the closest feature! nnfeature = None minfound = False mindist = float("inf") # If the input layer's geometry type is point, or has been # approximated to point (centroid), then a join index will # be used. # if ((QgsWkbTypes.geometryType(self.inpWkbType) == QgsWkbTypes.PointGeometry and # not QgsWkbTypes.isMultiType(self.inpWkbType)) or self.approximateinputgeom): if (self.approximateinputgeom or self.inpWkbType == QgsWkbTypes.Point or self.inpWkbType == QgsWkbTypes.Point25D): # Are there points on the join side? # Then the index nearest neighbour function is sufficient # if ((QgsWkbTypes.geometryType(self.joinWkbType) == QgsWkbTypes.PointGeometry and # not QgsWkbTypes.isMultiType(self.joinWkbType)) or self.usejoinlayerapprox): if (self.usejoinlayerapprox or self.joinWkbType == QgsWkbTypes.Point or self.joinWkbType == QgsWkbTypes.Point25D): # Is it a self join? if self.selfjoin: # Have to consider the two nearest neighbours nearestids = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) fch = 0 # Which of the two features to choose if (nearestids[0] == infeatureid and len(nearestids) > 1): # The first feature is the same as the input # feature, so choose the second one fch = 1 # Get the feature! if False: #if self.selectedjoonly: # This caused problems (wrong results) in QGIS 3.0.1 nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestids[fch]))) else: nnfeature = next(self.joinvl.getFeatures( QgsFeatureRequest(nearestids[fch]))) # Not a self join else: # Not a self join, so we search for only the # nearest neighbour (1) nearestids = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1) # Get the feature! if len(nearestids) > 0: nearestid = nearestids[0] nnfeature = next(self.joinvl.getFeatures( QgsFeatureRequest(nearestid))) #else: #if self.selectedjoonly: # nnfeature = next(self.joinvl.getSelectedFeatures( # QgsFeatureRequest(nearestid))) if nnfeature is not None: mindist = inputgeom.distance(nnfeature.geometry()) minfound = True # Not points on the join side # Handle common (non multi) non-point geometries elif (self.joinWkbType == QgsWkbTypes.Polygon or self.joinWkbType == QgsWkbTypes.Polygon25D or self.joinWkbType == QgsWkbTypes.LineString or self.joinWkbType == QgsWkbTypes.LineString25D): # Use the join layer index to speed up the join when # the join layer geometry type is polygon or line # and the input layer geometry type is point or a # point approximation nearestids = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1) # Possibe index out of range!!! ??? nearestindexid = nearestids[0] # Check for self join (possible if approx input) if self.selfjoin and nearestindexid == infeatureid: # Self join and same feature, so get the # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) # Possibe index out of range!!! ??? nearestindexid = nearestindexes[0] if (nearestindexid == infeatureid and len(nearestindexes) > 1): nearestindexid = nearestindexes[1] # If exclude containing, check for containment if self.excludecontaining: contained = False nearfeature = next(self.joinvl.getFeatures( QgsFeatureRequest(nearestindexid))) # Check for containment if nearfeature.geometry().contains(inputgeom): contained = True if inputgeom.contains(nearfeature.geometry()): contained = True numberofnn = 2 # Assumes that nearestNeighbor returns hits in the same # sequence for all numbers of nearest neighbour while contained: if self.abort is True: break nearestindexes = self.joinlind.nearestNeighbor( inputgeom.asPoint(), numberofnn) if len(nearestindexes) < numberofnn: nearestindexid = nearestindexes[numberofnn - 2] self.status.emit('No non-containing geometries!') break else: nearestindexid = nearestindexes[numberofnn - 1] # Seems to respect selection...? nearfeature = next(self.joinvl.getFeatures( QgsFeatureRequest(nearestindexid))) # Check for containment # Works! if nearfeature.geometry().contains( inputgeom): contained = True elif inputgeom.contains( nearfeature.geometry()): contained = True else: contained = False numberofnn = numberofnn + 1 # end while # Get the feature among the candidates from the index #if self.selectedjoonly: # # Does not get the correct feature! # nnfeature = next(self.joinvl.getSelectedFeatures( # QgsFeatureRequest(nearestindexid))) # This seems to work also in the presence of selections nnfeature = next(self.joinvl.getFeatures( QgsFeatureRequest(nearestindexid))) mindist = inputgeom.distance(nnfeature.geometry()) if mindist == 0: insidep = nnfeature.geometry().contains( inputgeom.asPoint()) # self.status.emit('0 distance! - ' + str(nearestindexid)) # self.status.emit('Inside: ' + str(insidep)) px = inputgeom.asPoint().x() py = inputgeom.asPoint().y() # Search the neighbourhood closefids = self.joinlind.intersects(QgsRectangle( px - mindist, py - mindist, px + mindist, py + mindist)) for closefid in closefids: if self.abort is True: break # Check for self join and same feature if self.selfjoin and closefid == infeatureid: continue # If exclude containing, check for containment if self.excludecontaining: # Seems to respect selection...? closefeature = next(self.joinvl.getFeatures( QgsFeatureRequest(closefid))) # Check for containment if closefeature.geometry().contains( inputgeom.asPoint()): continue if False: #if self.selectedjoonly: closef = next(self.joinvl.getSelectedFeatures( QgsFeatureRequest(closefid))) else: closef = next(self.joinvl.getFeatures( QgsFeatureRequest(closefid))) thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: # self.status.emit(' Mindist = 0!') break # Other geometry on the join side (multi and more) else: # Join with no index use # Go through all the features from the join layer! for inFeatJoin in self.joinf: if self.abort is True: break joingeom = inFeatJoin.geometry() thisdistance = inputgeom.distance(joingeom) if thisdistance < 0: self.status.emit("Warning: Join feature with " "missing geometry: " + str(inFeatJoin.id())) continue # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break # non (simple) point input geometries (could be multipoint) else: if (self.nonpointexactindex): # Use the spatial index on the join layer (default). # First we do an approximate search # Get the input geometry centroid centroid = infeature.geometry().centroid() centroidgeom = centroid.asPoint() # Find the nearest neighbour (index geometries only) # Possibe index out of range!!! ??? nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0] # Check for self join if self.selfjoin and nearestid == infeatureid: # Self join and same feature, so get the two # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( centroidgeom, 2) nearestid = nearestindexes[0] if nearestid == infeatureid and len(nearestindexes) > 1: nearestid = nearestindexes[1] # Get the feature! if False: #if self.selectedjoonly: nnfeature = next(self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestid))) else: nnfeature = next(self.joinvl.getFeatures( QgsFeatureRequest(nearestid))) mindist = inputgeom.distance(nnfeature.geometry()) # Calculate the search rectangle (inputgeom BBOX inpbbox = infeature.geometry().boundingBox() minx = inpbbox.xMinimum() - mindist maxx = inpbbox.xMaximum() + mindist miny = inpbbox.yMinimum() - mindist maxy = inpbbox.yMaximum() + mindist # minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist) # maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist) # miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist) # maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist) searchrectangle = QgsRectangle(minx, miny, maxx, maxy) # Fetch the candidate join geometries closefids = self.joinlind.intersects(searchrectangle) # Loop through the geometries and choose the closest # one for closefid in closefids: if self.abort is True: break # Check for self join and identical feature if self.selfjoin and closefid == infeatureid: continue if False: #if self.selectedjoonly: closef = next(self.joinvl.getSelectedFeatures( QgsFeatureRequest(closefid))) else: closef = next(self.joinvl.getFeatures( QgsFeatureRequest(closefid))) thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Check all the features of the join layer! mindist = float("inf") # should not be necessary for inFeatJoin in self.joinf: if self.abort is True: break joingeom = inFeatJoin.geometry() thisdistance = inputgeom.distance(joingeom) if thisdistance < 0: self.status.emit("Warning: Join feature with " "missing geometry: " + str(inFeatJoin.id())) continue # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break if not self.abort: # self.status.emit('Near feature - ' + str(nnfeature.id())) # Collect the attribute atMapA = infeature.attributes() if nnfeature is not None: atMapB = nnfeature.attributes() else: atMapB = [] for thefield in self.joinvl.fields(): atMapB.extend([None]) attrs = [] attrs.extend(atMapA) attrs.extend(atMapB) attrs.append(mindist) # Create the feature outFeat = QgsFeature() # Use the original input layer geometry!: outFeat.setGeometry(infeature.geometry()) # Use the modified input layer geometry (could be # centroid) # outFeat.setGeometry(inputgeom) # Add the attributes outFeat.setAttributes(attrs) # self.calculate_progress() self.features.append(outFeat) # self.mem_joinl.dataProvider().addFeatures([outFeat]) # end of do_indexjoin 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('NNJoinEngine', message)
class PubTool: def __init__(self, iface): self.iface = iface self.index_built = False def initGui(self): self.action = QAction('Pub!', self.iface.mainWindow()) self.action.triggered.connect(self.run) self.iface.addToolBarIcon(self.action) # build map tool self.mapTool = QgsMapToolEmitPoint(self.iface.mapCanvas()) self.mapTool.canvasClicked.connect(self.handle_click) #build projection transformer self.pj_transformer = QgsCoordinateTransform(\ QgsCoordinateReferenceSystem("EPSG:4326"),\ QgsCoordinateReferenceSystem("EPSG:5514"),\ QgsProject.instance()) self.allowed_projections = ["EPSG:4326", "EPSG:5514"] #Calculate distance self.distance_calculator = QgsDistanceArea() self.distance_calculator.setSourceCrs( QgsCoordinateReferenceSystem("EPSG:5514")) def unload(self): self.iface.removeToolBarIcon(self.action) del self.action, self.index, self.mapTool del self.pj_transformer, self.allowed_projections, self.index_built def build_index(self): self.index = QgsSpatialIndex(self.iface.activeLayer().getFeatures()) self.index_built = True def run(self): if (not self.index_built): self.build_index() self.iface.mapCanvas().setMapTool(self.mapTool) def get_nearest_point_name(self, point): # Check for projection c_proj = self.iface.mapCanvas().mapSettings().destinationCrs().authid() if c_proj not in self.allowed_projections: raise Exception( f'Your canvas projection {c_proj} is not allowed with plugin pub-tools;\ allowed projections: {self.allowed_projections}') elif c_proj == "EPSG:4326": point = QgsPoint(point) point.transform(self.pj_transformer) point = QgsPointXY(point) nearest_id = self.index.nearestNeighbor(point, 1) request = QgsFeatureRequest() request.setFilterFids(nearest_id) #TODO: Check if vector layer is activated! features = self.iface.activeLayer().getFeatures(request) # https://qgis.org/pyqgis/master/core/QgsSpatialIndex.html?highlight=spatialindex#module-QgsSpatialIndex elem = next(features, False) if not elem: raise Exception("No pub close") #TODO: Check if feature has Name attribute! return elem.attribute("Name") def handle_click(self, point): try: nearest = self.get_nearest_point_name(point) msg = f'You are at x: {point.x()} y: {point.y()}\ \nNearest point to you: {nearest}' QMessageBox.information(None, 'Pub Tool', msg) except Exception as ex: self.iface.messageBar().pushMessage("Error", str(ex), level=Qgis.Critical)
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
class SequenceGenerator: def __init__(self, centroid_layer, trajectory_layer, feedback, timezone, weight_field=None): centroids = [f for f in centroid_layer.getFeatures()] self.cell_index = QgsSpatialIndex() for f in centroids: self.cell_index.insertFeature(f) self.id_to_centroid = { f.id(): [f, [0, 0, 0, 0, 0]] for (f) in centroids } self.timezone = timezone self.weight_field = weight_field if weight_field is not None: self.weightIdx = trajectory_layer.fields().indexFromName( weight_field) else: self.weightIdx = None self.sequences = {} n_traj = float(trajectory_layer.featureCount()) for i, traj in enumerate(trajectory_layer.getFeatures()): self.evaluate_trajectory(traj) feedback.setProgress(i / n_traj * 100) def evaluate_trajectory(self, trajectory): points = trajectory.geometry().asPolyline() this_sequence = [] weight = 1 if self.weight_field is None else trajectory.attributes( )[self.weightIdx] prev_cell_id = None for i, pt in enumerate(points): nn_id = self.cell_index.nearestNeighbor(pt, 1)[0] nearest_cell = self.id_to_centroid[nn_id][0] nearest_cell_id = nearest_cell.id() if len(this_sequence) >= 1: prev_cell_id = this_sequence[-1] if nearest_cell_id != prev_cell_id: if (prev_cell_id, nearest_cell_id) in self.sequences: self.sequences[(prev_cell_id, nearest_cell_id)] += weight else: self.sequences[(prev_cell_id, nearest_cell_id)] = weight if nearest_cell_id != prev_cell_id: # we have changed to a new cell --> up the counter m = trajectory.geometry().vertexAt(i).m() if math.isnan(m): m = 0 t = datetime(1970, 1, 1) + timedelta( seconds=m) + timedelta(hours=self.timezone) h = t.hour self.id_to_centroid[nn_id][1][0] += weight self.id_to_centroid[nn_id][1][int(h / 6) + 1] += weight this_sequence.append(nearest_cell_id) def create_flow_lines(self): lines = [] for key, value in self.sequences.items(): p1 = self.id_to_centroid[key[0]][0].geometry().asPoint() p2 = self.id_to_centroid[key[1]][0].geometry().asPoint() p1 = QgsPoint(p1.x(), p1.y()) p2 = QgsPoint(p2.x(), p2.y()) feat = QgsFeature() feat.setGeometry(QgsGeometry.fromPolyline([p1, p2])) feat.setAttributes([key[0], key[1], value]) lines.append(feat) return lines
def processAlgorithm(self, parameters, context, feedback): try: import processing as st import networkx as nx except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) feedback.reportError(QCoreApplication.translate('Error',' ')) feedback.reportError(QCoreApplication.translate('Error','Error loading modules - please install the necessary dependencies')) return {} Network = self.parameterAsLayer(parameters, self.Network, context) Sources = self.parameterAsLayer(parameters, self.Sources, context) d = parameters[self.lineDen] Precision = 6 explode = st.run("native:explodelines",{'INPUT':Network,'OUTPUT':'memory:'},context=context,feedback=feedback) wF = parameters[self.Weight] fet = QgsFeature() fs = QgsFields() skip = ['Distance'] fields = Network.fields() for field in fields: if field.name() not in skip: fs.append( QgsField(field.name() ,field.type() )) fs.append(QgsField('Distance', QVariant.Double)) (writer, dest_id) = self.parameterAsSink(parameters, self.SP, context, fs, QgsWkbTypes.LineString, Network.sourceCrs()) index = QgsSpatialIndex(explode['OUTPUT'].getFeatures()) orig_data = {feature.id():feature for feature in explode['OUTPUT'].getFeatures()} srcs,data = [],{} if d > 0 and Sources.geometryType() == 1: params = {'INPUT':Sources,'INTERVAL':d,'OUTPUT':'memory:'} densify = st.run("native:densifygeometriesgivenaninterval",params,context=context,feedback=feedback) infc = densify['OUTPUT'] else: infc = Sources params = {'INPUT':infc,'OUTPUT':'memory:'} #Create nodes vertices = st.run('native:extractvertices',params,context=context,feedback=feedback) G = nx.Graph() feedback.pushInfo(QCoreApplication.translate('Model','Defining Source Nodes')) total = 100.0/vertices['OUTPUT'].featureCount() for enum,feature in enumerate(vertices['OUTPUT'].getFeatures()): #Find source node try: if total > 0: feedback.setProgress(int(enum*total)) pnt = feature.geometry().asPoint() startx,starty = (round(pnt.x(),Precision),round(pnt.y(),Precision)) featFIDs = index.nearestNeighbor(QgsPointXY(startx,starty), 2) d = 1e10 for FID in featFIDs: feature2 = orig_data[FID] testGeom = QgsGeometry.fromPointXY(QgsPointXY(startx,starty)) dist = QgsGeometry.distance(testGeom,feature2.geometry()) if dist < d: #Find closest vertex in graph to source point d = dist geom = feature2.geometry() start,end = geom.asPolyline() startx2,starty2 = (round(start.x(),Precision),round(start.y(),Precision)) endx2,endy2 = (round(end.x(),Precision),round(end.y(),Precision)) testGeom2 = QgsGeometry.fromPointXY(QgsPointXY(startx2,starty2)) testGeom3 = QgsGeometry.fromPointXY(QgsPointXY(endx2,endy2)) near = QgsGeometry.distance(testGeom2,testGeom) near2 = QgsGeometry.distance(testGeom3,testGeom) if near < near2: x,y = startx2,starty2 else: x,y = endx2,endy2 dx = startx - x dy = starty - y w = math.sqrt((dx**2)+(dy**2)) srcs.append((startx,starty)) #srcs.append((x,y)) G.add_edge((startx,starty),(x,y),weight=w) G.add_edge((x,y),(startx,starty),weight=w) except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) total = 100.0/explode['OUTPUT'].featureCount() feedback.pushInfo(QCoreApplication.translate('Model','Building Graph')) for enum,feature in enumerate(explode['OUTPUT'].getFeatures()): #Build Graph try: if total > 0: feedback.setProgress(int(enum*total)) geom = feature.geometry() if geom.isMultipart(): start,end = geom.asMultiPolyline()[0] else: start,end = geom.asPolyline() startx,starty = (round(start.x(),Precision),round(start.y(),Precision)) endx,endy = (round(end.x(),Precision),round(end.y(),Precision)) if wF: w = float(feature[wF])*feature.geometry().length() else: w = feature.geometry().length() G.add_edge((startx,starty),(endx,endy),weight=w) #G.add_edge((endx,endy),(startx,starty),weight=w) rows = [] for field in fields: if field.name() not in skip: rows.append(feature[field.name()]) data[((endx,endy),(startx,starty))] = rows except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) feedback.pushInfo(QCoreApplication.translate('Model','Creating Fracture Network')) try: if len(srcs) > 0: total = 100.0/len(srcs) lengths = None for enum,source in enumerate(srcs): feedback.setProgress(int(enum*total)) if G.has_node(source): if lengths != None: lengths2 = nx.single_source_dijkstra_path_length(G,source) for k in lengths2.keys(): if k in lengths: v = lengths2[k] v2 = lengths[k] if v < v2: lengths[k] = v else: lengths[k] = lengths2[k] del lengths2 else: lengths = nx.single_source_dijkstra_path_length(G,source) if lengths: #if connection exists for edge in G.edges(): L = -9999 for node in edge: if node in lengths: dist = lengths[node] if L == -9999: L = dist prev = node elif dist < L: L = dist midS = node midE = prev else: midS = prev midE = node if L != -9999: # Check if there is a connection if (edge[1],edge[0]) in data: rows = data[(edge[1],edge[0])] elif (edge[0],edge[1]) in data: rows = data[(edge[0],edge[1])] else: continue midEx,midEy = midE midSx,midSy = midS points = [QgsPointXY(midSx,midSy),QgsPointXY(midEx,midEy)] outGeom = QgsGeometry.fromPolylineXY(points) fet.setGeometry(outGeom) rows.extend([float(L)]) fet.setAttributes(rows) writer.addFeature(fet) except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) return {self.SP:dest_id}