On the base of @christoph script, I adapt it to take in account the height and width of the layout, and I add the scale:
from qgis.PyQt.QtCore import QVariant
from qgis import processing
from qgis.processing import alg
from qgis.core import QgsProcessing, QgsGeometry, QgsFeature, QgsWkbTypes, QgsFeatureSink, QgsFields, QgsField, QgsPointXY, QgsSettings
@alg(name="algRectAlongLines", label="Generate rectangles along lines", group="Drosera", group_label="drosera")
@alg.input(type=alg.SOURCE, name="INPUT", label="Select Line layer", types=[QgsProcessing.TypeVectorLine])
@alg.input(type=alg.NUMBER, name="WIDTH", label="Enter Layout Rectangle Width (mm)", default=QgsSettings().value("algRectAlongLines/width", 10))
@alg.input(type=alg.NUMBER, name="HEIGHT", label="Enter Layout Rectangle Height (mm)", default=QgsSettings().value("algRectAlongLines/height", 10))
@alg.input(type=alg.NUMBER, name="SCALE", label="Enter Layout Map Scale (e.g. 1000 for 1:1000)", default=QgsSettings().value("algRectAlongLines/scale", 1000))
@alg.input(type=alg.NUMBER, name="OVERLAP", label="Enter Rectangle Overlap [%]", default=QgsSettings().value("algRectAlongLines/overlap", 0))
@alg.input(type=alg.NUMBER, name="TOLERANCE", label="Enter Tolerance for Line Simplification", default=QgsSettings().value("algRectAlongLines/tolerance", 0))
@alg.input(type=alg.BOOL, name="ALIGN", label="Align Rectangles with Line Features", default=QgsSettings().value("algRectAlongLines/align", True))
@alg.input(type=alg.SINK, name="OUTPUT", label="Output layer")
def algRectAlongLines(self, parameters, context, feedback, inputs):
"""
"""
s = QgsSettings()
width_mm = self.parameterAsDouble(parameters, 'WIDTH', context)
height_mm = self.parameterAsDouble(parameters, 'HEIGHT', context)
scale = self.parameterAsDouble(parameters, 'SCALE', context)
if width_mm != s.value("algRectAlongLines/width"):
s.setValue("algRectAlongLines/width", width_mm)
if height_mm != s.value("algRectAlongLines/height"):
s.setValue("algRectAlongLines/height", height_mm)
if scale != s.value("algRectAlongLines/scale"):
s.setValue("algRectAlongLines/scale", scale)
width = (width_mm / 1000.0) * scale
height = (height_mm / 1000.0) * scale
overlap = self.parameterAsDouble(parameters, 'OVERLAP', context)
if overlap != s.value("algRectAlongLines/overlap"):
s.setValue("algRectAlongLines/overlap", overlap)
if overlap > 0:
overlap = overlap / 100.0
tolerance = self.parameterAsDouble(parameters, 'TOLERANCE', context)
if tolerance != s.value("algRectAlongLines/tolerance"):
s.setValue("algRectAlongLines/tolerance", tolerance)
align = self.parameterAsBool(parameters, 'ALIGN', context)
if align != s.value("algRectAlongLines/align"):
s.setValue("algRectAlongLines/align", align)
i = 1
source = self.parameterAsSource(parameters, 'INPUT', context)
fields = QgsFields()
fields.append(QgsField('src_fid', QVariant.Int))
fields.append(QgsField('angle', QVariant.Double))
fields.append(QgsField('sort_order', QVariant.Int))
(sink, dest_id) = self.parameterAsSink(parameters, 'OUTPUT',
context, fields, QgsWkbTypes.Polygon , source.sourceCrs())
features = source.getFeatures()
for feature in features:
geom = feature.geometry()
if tolerance != 0:
geom = geom.simplify(tolerance)
curs = 0
numpages = geom.length() / width
step = 1.0 / numpages
stepnudge = (1.0 - overlap) * step
currangle = 0
while curs < 1:
startpoint = geom.interpolate(curs * geom.length())
endpoint = geom.interpolate((curs + step) * geom.length())
if not endpoint:
endpoint = geom.interpolate(geom.length())
x_start = startpoint.asPoint().x()
y_start = startpoint.asPoint().y()
x_end = endpoint.asPoint().x()
y_end = endpoint.asPoint().y()
x_mid = (x_start + x_end) / 2
y_mid = (y_start + y_end) / 2
w2 = width / 2.0
h2 = height / 2.0
minx = -w2
miny = -h2
maxx = w2
maxy = h2
poly = QgsGeometry().fromWkt(
f'POLYGON(({minx} {miny}, {minx} {maxy},{maxx} {maxy}, {maxx} {miny}, {minx} {miny}))'
)
feat = QgsFeature()
feat.setFields(fields)
if align:
azimuth = startpoint.asPoint().azimuth(endpoint.asPoint())
currangle = (azimuth + 270) % 360
poly.rotate(currangle, QgsPointXY(0, 0))
feat['angle'] = currangle
else:
feat['angle'] = 0
poly.translate(-width * overlap, 0)
poly.translate(x_mid, y_mid)
poly.asPolygon()
curs = curs + stepnudge
feat['src_fid'] = feature.id()
feat['sort_order'] = i
feat.setGeometry(poly)
sink.addFeature(feat, QgsFeatureSink.FastInsert)
i += 1
results = {}
results['OUTPUT'] = dest_id
return results
But I noticed a problem when the line has "sharpe" angle, cause the start and end point are "centered". So if the line goes "outside" the rectangle, the whole line is not taking in account.

So I adapt it to calculate the azimut at the midpoint, and not between the start and endpoint:
from qgis.PyQt.QtCore import QVariant
from qgis import processing
from qgis.processing import alg
from qgis.core import (
QgsProcessing,
QgsGeometry,
QgsFeature,
QgsWkbTypes,
QgsFeatureSink,
QgsFields,
QgsField,
QgsPointXY,
QgsSettings
)
@alg(name="algRectAlongLines", label="Generate rectangles along lines", group="Drosera", group_label="drosera")
@alg.input(type=alg.SOURCE, name="INPUT", label="Select Line layer", types=[QgsProcessing.TypeVectorLine])
@alg.input(type=alg.NUMBER, name="WIDTH", label="Enter Layout Rectangle Width (mm)", default=QgsSettings().value("algRectAlongLines/width", 10))
@alg.input(type=alg.NUMBER, name="HEIGHT", label="Enter Layout Rectangle Height (mm)", default=QgsSettings().value("algRectAlongLines/height", 10))
@alg.input(type=alg.NUMBER, name="SCALE", label="Enter Layout Map Scale (e.g. 1000 for 1:1000)", default=QgsSettings().value("algRectAlongLines/scale", 1000))
@alg.input(type=alg.NUMBER, name="OVERLAP", label="Enter Rectangle Overlap [%]", default=QgsSettings().value("algRectAlongLines/overlap", 0))
@alg.input(type=alg.NUMBER, name="TOLERANCE", label="Enter Tolerance for Line Simplification", default=QgsSettings().value("algRectAlongLines/tolerance", 0))
@alg.input(type=alg.BOOL, name="ALIGN", label="Align Rectangles with Line Features", default=QgsSettings().value("algRectAlongLines/align", True))
@alg.input(type=alg.SINK, name="OUTPUT", label="Output layer")
def algRectAlongLines(self, parameters, context, feedback, inputs):
"""
"""
s = QgsSettings()
width_mm = self.parameterAsDouble(parameters, 'WIDTH', context)
height_mm = self.parameterAsDouble(parameters, 'HEIGHT', context)
scale = self.parameterAsDouble(parameters, 'SCALE', context)
if width_mm != s.value("algRectAlongLines/width"):
s.setValue("algRectAlongLines/width", width_mm)
if height_mm != s.value("algRectAlongLines/height"):
s.setValue("algRectAlongLines/height", height_mm)
if scale != s.value("algRectAlongLines/scale"):
s.setValue("algRectAlongLines/scale", scale)
width = (width_mm / 1000.0) * scale
height = (height_mm / 1000.0) * scale
overlap = self.parameterAsDouble(parameters, 'OVERLAP', context)
if overlap != s.value("algRectAlongLines/overlap"):
s.setValue("algRectAlongLines/overlap", overlap)
if overlap > 0:
overlap = overlap / 100.0
tolerance = self.parameterAsDouble(parameters, 'TOLERANCE', context)
if tolerance != s.value("algRectAlongLines/tolerance"):
s.setValue("algRectAlongLines/tolerance", tolerance)
align = self.parameterAsBool(parameters, 'ALIGN', context)
if align != s.value("algRectAlongLines/align"):
s.setValue("algRectAlongLines/align", align)
source = self.parameterAsSource(parameters, 'INPUT', context)
fields = QgsFields()
fields.append(QgsField('src_fid', QVariant.Int))
fields.append(QgsField('angle', QVariant.Double))
fields.append(QgsField('sort_order', QVariant.Int))
(sink, dest_id) = self.parameterAsSink(parameters, 'OUTPUT', context, fields, QgsWkbTypes.Polygon, source.sourceCrs())
i = 1
features = source.getFeatures()
for feature in features:
geom = feature.geometry()
if tolerance != 0:
geom = geom.simplify(tolerance)
geom_length = geom.length()
curs = 0
numpages = geom_length / width
if numpages == 0:
continue
step = 1.0 / numpages
stepnudge = (1.0 - overlap) * step
while curs < 1:
startpoint = geom.interpolate(curs * geom_length)
endpoint = geom.interpolate((curs + step) * geom_length)
if not endpoint:
endpoint = geom.interpolate(geom_length)
x_start = startpoint.asPoint().x()
y_start = startpoint.asPoint().y()
x_end = endpoint.asPoint().x()
y_end = endpoint.asPoint().y()
x_mid = (x_start + x_end) / 2
y_mid = (y_start + y_end) / 2
w2 = width / 2.0
h2 = height / 2.0
minx = -w2
miny = -h2
maxx = w2
maxy = h2
poly = QgsGeometry().fromWkt(
f'POLYGON(({minx} {miny}, {minx} {maxy},{maxx} {maxy}, {maxx} {miny}, {minx} {miny}))'
)
feat = QgsFeature()
feat.setFields(fields)
if align:
center_dist = (curs + step * 0.5) * geom_length
epsilon = geom_length * 0.001
p1 = geom.interpolate(max(center_dist - epsilon, 0)).asPoint()
p2 = geom.interpolate(min(center_dist + epsilon, geom_length)).asPoint()
azimuth = p1.azimuth(p2)
currangle = (azimuth + 270) % 360
poly.rotate(currangle, QgsPointXY(0, 0))
feat['angle'] = currangle
else:
feat['angle'] = 0
poly.translate(-width * overlap, 0)
poly.translate(x_mid, y_mid)
curs = curs + stepnudge
feat['src_fid'] = feature.id()
feat['sort_order'] = i
feat.setGeometry(poly)
sink.addFeature(feat, QgsFeatureSink.FastInsert)
i += 1
results = {}
results['OUTPUT'] = dest_id
return results
It works:
