3

I've got a polygon shape (Catchments) and a lines shape (Rivers)

Polygons representing catchments in orange, lines representing rivers in blue

Each river body has an ID. What I need is to assign the ID of the predominant river within a polygon. For example, in 1 polygon there might be many rivers with different IDs ("MSPF_EM_CD" is the ID of the rivers):

enter image description here

As can be seen in the picture, in the attribute table of the rivers shape there is also a column with the length of each river line. What I need is to identify the longest line within each polygon, and add the ID of this longest line to the polygon shape, so I can get a new column in the polygon attribute table with the ID of the longest river within each polygon.

I am using QGIS 2.18.1 Las Palmas, in Windows 10.

6
  • 1
    Did you previously intersect the rivers with the catchments? If yes, my solution is ready; if not, the values stored in the "Shape_Leng" field could not be used because they would refer to the whole feature and not only to the portion within the catchment feature.
    – mgri
    Jan 16, 2017 at 10:28
  • Hi @HowToInQGIS I've just done the intersection of rivers with cathments (rivers as the input and catchment as the intersect layer) What I get is a new line shape with the attributes of both layers. Is that they right way? When I do the opposite, (rivers as the intersect layer) I get an empty file, no attributes, no polygons. Jan 16, 2017 at 11:20
  • 1
    What I want to know is if there is any "crossing" river on your polygons. From the example you posted, it seems that you already intersected the rivers with the polygons: if this is the case, I imagine that the values stored in the "Shape_Leng" field refer to the intersected feature; otherwise, I need to compute the new length of the intersected feature (without the possibility to use the "Shape_Leng" field).
    – mgri
    Jan 16, 2017 at 11:35
  • 1
    Please have a look to my answer and let me know if it works for you and if the result is what you expect (I assume that you have already intersected the rivers with the catchments, otherwise I need to modify the solution without considering the "Shape_Leng" field).
    – mgri
    Jan 16, 2017 at 13:45
  • 1
    @HowToInQGIS Once again, thanks, thanks and thanks again. You're my champion. See below my comment for the clarification on length, that's the only thing I had to change so the code works. Let me know what can I do for you!! Jan 16, 2017 at 17:54

1 Answer 1

2

NOTE I edited the code (and the explanation of how it works) because there were wrong results in some specific cases.


I propose a solution with PyQGIS: starting from a linestring layer and a polygon layer, it looks for the line features within each polygon feature and then creates a new polygon memory layer (called "catchments_new") which stores the same attributes of the starting polygon layer plus one additional field (called "PREDOMIN_ID") containing the ID of the predominant river within it.

This is the code:

##Rivers=vector line
##Catchments=vector polygon

from qgis.core import *
from qgis.PyQt.QtCore import QVariant

layer1 = processing.getObject(Rivers)
crs = layer1.crs().toWkt()
layer2 = processing.getObject(Catchments)

# Create the output layer
outLayer = QgsVectorLayer('Polygon?crs='+ crs, 'catchments_new' , 'memory')
prov = outLayer.dataProvider()
fields = layer2.pendingFields() # Fields from the input layer
fields.append(QgsField('PREDOMIN_ID', QVariant.String, '', 50, 0)) # Name for the new field in the output layer
prov.addAttributes(fields) # Add input layer fields to the outLayer
outLayer.updateFields()

index = QgsSpatialIndex() # Spatial index
for ft in layer1.getFeatures():
    index.insertFeature(ft)

for feat in layer2.getFeatures():
    attrs = feat.attributes()
    inGeom = feat.geometry()
    idsList = index.intersects(inGeom.boundingBox())
    if len(idsList) > 0:
        req = QgsFeatureRequest().setFilterFids(idsList)
        temp_river_lengths = {} # Store the ids and lengths that are within the current polygon feature
        for elem in layer1.getFeatures(req):
            temp_geometry = elem.geometry()
            if temp_geometry.intersects(inGeom):
                itx = temp_geometry.intersection(inGeom)
                if elem[0] not in temp_river_lengths.keys():
                    temp_river_lengths[elem[0]] = itx.length()
                else:
                    temp_river_lengths[elem[0]]  += itx.length()
        k=list(temp_river_lengths.keys())        
        v=list(temp_river_lengths.values())
        if len(k) > 0:
            predom_id = k[v.index(max(v))]
            attrs.append(predom_id)
    outGeom = QgsFeature()
    outGeom.setAttributes(attrs) # Output attributes
    outGeom.setGeometry(inGeom) # Output geometry
    prov.addFeatures([outGeom]) # Output feature

# Add the layer to the Layers panel
QgsMapLayerRegistry.instance().addMapLayer(outLayer)
5
  • It worked!! Just 1 clarification: I had to create a new layer, bcs in the original layer there were lines crossing polygons, so I had to do an intersect so no lines crossed polygons. Consequence is that in the new intersect layer, the field "Shape_Leng" doesn't work, as it doesn't represent the real length of each line (Some of them were cut in the intersect process) So I computed a new field called "Shp_Leng" in which I calculated real length of each line using field calculator operation $length. Just by changing that detail in the code, it works! @HowToInQGIS you came from heavens to save me Jan 16, 2017 at 17:49
  • 1
    I'm glad it worked for you and now I know you are the king of "predominant" operations! =). The operation you described is correct, but the code would however work without any editing if you update the existing "Shape_Leng" field in your intersected layer instead of creating a new field (as showed in this image, select the "Shape_Leng" field and then click the Update All button. Then, save the edit and run the code).
    – mgri
    Jan 16, 2017 at 18:11
  • 1
    The preliminary intersection was only necessary for the using of the code that I posted, otherwise it could be obviously possible to implement the intersection operation inside it.
    – mgri
    Jan 16, 2017 at 18:11
  • 1
    True, it was just enough if I had updated the existing "Shape_Leng" field, but I wanted to do it in a new field in order to compare the results (They varied a bit, don't know why). Thanks anyway, one day with a drink I can explain you why so many "predominant" operations ;) Jan 17, 2017 at 15:41
  • 1
    I think the calculations are slightly different because the results depend from the method and the software used (the name "Shape_Leng" is generally related to ArcGIS). However, the results differ after many decimals, but as you know it is not crucial at the basin scale. Thanks also for the drink, maybe one day we will talk about a collaboration =)
    – mgri
    Jan 17, 2017 at 18:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.