Calculating polygon's length along a line?

I need to calculate the length of polygons along / projected on a line. An image to show the idea: I have thousands of polygons to be "projected" on lines to verify how many meters of the line they belong to. The polygons are irregular, with different orientation, part of them intersect the line, part of them do not.

I've tried:

1. Locate features along line tool in ArcGIS Pro, but it only gives the lenght of line for overlapping part of a polygon (tool description here ESRI help)

2. Minimum Bounding Geometry with 'convex hull' option, but it only gives distance between antipodal points of a polygon, which is not parallell to the my line (tool description here ESRI help

Am I missing a tool for this in ArcGIS or QGIS? Would you have a solution for this?

• Is your line a straight line? – JiyuuSensei Aug 9 at 11:19
• Since you have a unique requirement, it doesn't seem unreasonable that such a tool does not exist. You could certainly code such a tool using trig functions and various geometry helper functions, but first you need to choose a software platform. – Vince Aug 9 at 11:27
• @JiyuuSensei no, actually it's a polyline network - like a road network or pipeline network. So this should work like calculating this "polygon length" for the closest line segment of the network. So it's basically the idea of how Locate features along line tool works like. – Konrad Gje Aug 9 at 13:57

Using QGIS:

(1) Create point layer of the polygon vertices using Extract vertices tool (in the QGIS Processing Toolbox > Vector geometry). (2) Open the attribute table of the newly created Vertices layer.

(3) Start the Field Calculator and;

(3A) Create a new field, let's call it min_poly to store the minimum distance and give an expression:

minimum(
line_locate_point(
geometry:=geometry(get_feature_by_id('Lines', '1')), point:=\$geometry),
group_by:="fid")

NB: you will need to change 'Lines' to your actual line layername, and '1' to the line id you have in your attribute table.

(3B) Create another new field, let's call it max_poly to store the maximum distance and give an expression:

maximum(
line_locate_point(
geometry:=geometry(get_feature_by_id('Lines', '1')), point:=\$geometry),
group_by:="fid") Since the Vertices layer keeps fid field from the original polygon ids, you will find min_poly and max_poly is the minimum/maximum distance along the line for each polygon.

(The above example shows the first polygon fid=1 spans from 8.688 to 24.45, while the second polygon fid=2 is from 35.062 to 42.496).

I don't know how to do it with QGIS or ArcGIS but what you want feels like a width of an oriented bounding box. As a proof of concept I rotated your sample image so that the projection line is horizontal. Then I digitized the polygons from the image and generated envelopes for them. Width of the envelope answers your question. What is missing is a tool that creates bounding box at a given angle. QGIS does have Oriented minimum bounding box tool but user can't give a fixed angle for that. Probably you could adopt the PostGIS solution from the accepted answer to this question Creating "oblique bounding box" with maximum width/height ratio?.

• This answer would be the best approach because it is very easy to obtain oriented bounding box in QGIS (with width and height of the envelope) but two sides of this oriented bounding box necessarily need to be parallels to reference line and this condition is not guaranteed. This is the problem with irregular polygon. – xunilk Aug 9 at 16:17

Based on @kazuhito's answer I put together a single, hacky expression in the QGIS Field Calculator that should do the same thing in one step.

However I can imagine this will be very resource-intensive on larger datasets. I think the problem is best suited to a Python implementation, which obviously handles referencing and iteration far better than the Field Calculator.

array_last(array_sort(array_foreach(
generate_series(1,num_points(\$geometry)-1),
line_locate_point(aggregate('lines','collect',\$geometry),
point_n(\$geometry,@element))),1))
- array_first(array_sort(array_foreach(
generate_series(1,num_points(\$geometry)-1),
line_locate_point(aggregate('lines','collect',\$geometry),
point_n(\$geometry,@element))),1))

This first creates an 'array' of node numbers using generate_series(), specifying the maximum as the number of nodes in each polygon - this is num_points(\$geometry), minus 1 to skip the repeated first/last node.

You can then pass the values of this array through a function to generate another array using array_foreach(). Here we pass the polygon node number (represented as @element) to point_n(), which returns the actual geometry of that node, then we feed that into line_locate_point() to determine its length along the specified line (see Important Note below).

The resulting array is then sorted in ascending order using array_sort() which then lets us get the "leftmost" and "rightmost" distances along the line using array_last() and array_first(). Subtract the two and the result is the "length" of the polygon along the line.

See below for an example of the above expression shown as a label in the polygons (plus "leftmost" and "rightmost" line distances split off from the above expression). For comparison I have also included extracted vertices and relevant line distance values. Green vertices are the "leftmost" and "rightmost" vertices along the line. Note the top-left polygon where the green point is actually further along the line than the point to the right below it, due to the angle of the line...

Important Note:

The line layer geometry is referenced here using aggregate(). You will need to change the layer name ('lines') as required, and if you have multiple lines, you should add a filter to specify which line you want to compare it with, e.g.: aggregate('lines','collect',\$geometry,"name"='TrainLine1') . To make this automatically work on the closest line I really recommend SQL or Python over Field Calc.

Also, this calculates the "length" of the Polygon ALONG the line, including if the line is kinked per my example. If you want the straight-line distance... maybe calculate the distance() between the relevant nodes? Best approach in QGIS to do that (considering thousands of polygons to be "projected" on a reference line) is determining the bounding box rotated by the angle between reference line and X axis. This angle is easily determined by using Extract vertices tool (in the QGIS Processing Toolbox -> Vector geometry) for reference line. From the example of following image, required angle is determined with value in attributes table of Vertices layer employing this formula:

90 - 63.91873763467915 = 26.081262365320853 degrees. Introducing above angle in Rotate tool of Processing Toolbox and running it: is produced a rotated layer whose bounding box has in its attributes table the length of polygon projected on reference line. It can be finally observed by running this tool of Processing Toolbox: At following image, length of polygon projected on a line is the value found in field width. It can be observed at following image that the projected length of considered polygon, it would be as expected because rotated reference line by 26.081262365320853 degrees (obtained with Rotate tool of Processing Toolbox) is parallel to X axis. 