Using PyQGIS to buffer the width different than the height of a rectangle

So I have a polygon in QGIS (3.18) and it will always be a rectangle (I create it using minimum bounding geometry, oriented rectangle) I want to buffer the width out using some math to get the total width to a number divisible by a number input by the user, and I want to do the same to the height but the buffer amount will need to be different to get it divisible by the same number. What is the best way to go about this?

This is for a plugin so I need to do this in PyQGIS.

EDIT: the end goal here is to divide this rectangle up into a grid of squares (side length of whatever the user inputs), and the squares should fill the rectangle up without any partial squares. If there is an even better way to get to this end goal then great. But I figure I will need to get the dimensions of the rectangle correct first.

here is the code to create the rectangle:

``````        newconvexhulllayer = processing.run("qgis:minimumboundinggeometry", {
'INPUT': self.poly_layer,
'TYPE': 1,
'OUTPUT': 'memory:'})

``````

I don't know where to begin for code for the buffer since I need to buffer the width and height by a different amount. I'm not worried about the math I just need to know how to buffer the width and height differently in qgis.

• What do you want exactly ? Can you provide an example ? If I understand correctly, if you have a rectangle 100 x 20, and the user want to divide it by 5, you want to know the "buffer" distance, in this example 20 for the width and 4 for the height ? May 10 at 15:55
• lets say its 99 x 23, and the user input number is 20. then I will need to buffer the width out to get it to 100, and I will need to buffer the height out to get it to 40.
– tbob
May 10 at 16:02
• Coding questions are expected to contain code. What have you tried, and where are you stuck? May 10 at 16:45

Here a PyQGIS solution for 1 feature :

``````from functools import lru_cache

@lru_cache(maxsize=6)
def orientation_choices(num_coords: int, axis: str = "x") -> list:
if axis == "x":
if num_coords == 4:  # diagonal rectangle
return ["L", "L", "R", "R"]
elif num_coords == 2:  # aligned with North rectangle
return ["L", "R"]
elif num_coords == 3:  # diamond rectangle
return ["L", "0", "R"]

if axis == "y":
if num_coords == 4:  # diagonal rectangle
return ["B", "B", "T", "T"]
elif num_coords == 2:  # aligned with North rectangle
return ["B", "T"]
elif num_coords == 3:  # diamond rectangle
return ["B", "0", "T"]

def which_rect_corner(vertices: list, vertex: QgsPoint) -> str:
x_coords, y_coords = zip(*set((v.x(), v.y()) for v in vertices))
x_coords = sorted(x_coords)
y_coords = sorted(y_coords)
pos_x_vertex = x_coords.index(vertex.x())
pos_y_vertex = y_coords.index(vertex.y())
choices_x = orientation_choices(len(x_coords), "x")
choices_y = orientation_choices(len(y_coords), "y")
return f'{choices_y[pos_y_vertex]}{choices_x[pos_x_vertex]}'

# replace here the variables with true values
rect_feature_index = 1
rect_width = 99
rect_height = 23
user_input = 20

# replace layer by the vector layer output of the QGIS alg
layer = iface.activeLayer()
current_rect_geom = layer.getFeature(rect_feature_index).geometry()

new_rect_width = (rect_width // user_input + [1, 0][rect_width % user_input == 0]) * user_input
new_rect_height = (rect_height // user_input + [1, 0][rect_width % user_input == 0]) * user_input

x_coord_delta = (new_rect_width - rect_width) / 2
y_coord_delta = (new_rect_height - rect_height) / 2

corners = [
(
vertex,
which_rect_corner(current_rect_geom.vertices(), vertex)
)
for vertex in current_rect_geom.vertices()
]

for i, (vertex, corner) in enumerate(corners):
vertex_x, vertex_y = vertex.x(), vertex.y()
if corner == "BL":  # Bottom Left
vertex_x -= x_coord_delta
vertex_y -= y_coord_delta
elif corner == "TL":  # Top Left
vertex_x -= x_coord_delta
vertex_y += y_coord_delta
elif corner == "TR":  # Top Right
vertex_x += x_coord_delta
vertex_y += y_coord_delta
elif corner == "BR":  # Bottom Right
vertex_x += x_coord_delta
vertex_y -= y_coord_delta
elif corner == "B0":
vertex_y -= y_coord_delta
elif corner == "T0":
vertex_y += y_coord_delta
elif corner == "0L":
vertex_x -= x_coord_delta
elif corner == "0R":
vertex_x += x_coord_delta

# modify the vertex
current_rect_geom.moveVertex(vertex_x, vertex_y, i)

with edit(layer):
layer.changeGeometry(rect_feature_index, current_rect_geom)
``````
• Thanks so much for this. Seems that all works except for 2 corners have the x value go the wrong way and I can't seem to fix it. My work is pulling me a different direction for now so I'll have to try to trouble shoot this later. The example rectangle I'm working with for my data is sitting at about a 45 degree angle, so its close to a diamond shape, and the corners that have an issue are the very top one and the bottom one if that makes sense , the right most corner and left most are fine.
– tbob
May 11 at 22:13
• I didn't think about diamond shape rectangles, answer edited. May 12 at 7:27
• Thanks so much for this, but actually this one seems to not produce any change from the original geometry. here is the geometry I am using: <QgsGeometry: Polygon ((-497098.89258654043078423 -982100.14251322357449681, -520808.41675865603610873 -967862.51037820673082024, -544775.87417017761617899 -1007774.83606928959488869, -521066.34999806201085448 -1022012.46820430643856525, -497098.89258654043078423 -982100.14251322357449681))> I am in crs=esri:102008
– tbob
May 12 at 14:53
• I fixed a little thing but it doesn't resolve the problem. For your geometry, the top and bottom vertices change side after the "buffer", so I think that's for this reason it doesn't work, the addition of coordinates isn't in the good way. But I don't know how to fix that. May 12 at 19:21