5

I want to flip a vector polygon about an arbitrary axis. What I would like to do is select the polygon to flip, pick two points (A & B in image below) identifying the axis about which the flip will occur, and get a result that looks like the red-dashed polygon.

What I have tried: searching for 'Flip', 'Mirror', 'Transform' in QGIS processing tools and web searches.

As a workaround, I've been using the 'Shape Tools' plugin 'Flip Horiz/Vert' in combination with QGIS Vector Tools 'Rotate'.

vector flip about arbitrary line

1
  • Phew... I guess you need to create a line from your points, then extend it, then calculate the distance and direction of each polygon vertex to the line, then translate each vertex by that distance, and finally convert your vertices back to a line and that to a polygon. I doubt this is possible without creating a new layer.
    – Erik
    Jun 8, 2022 at 13:45

2 Answers 2

5

I wrote this code that reflects a polygon between a line created by two points. I don't know the best way for you to input the feature and the points, so the implementation is very basic and you'll probably need to fix some lines for your main purpose.

Note: I am not an expert mathematician nor a python programmer. Maybe this script is inefficient but the important thing is to get the idea.

Notes

  • This script is very basic, so it works only with single part polygon geometries.
  • This code modifies and overrides the features geometries, so be careful
  • This include snapping tools.

You only need to paste the code in the QGIS python console, then select the feature you want to reflect, run the code, left click first point of the reflection line right click second point of the reflection line.

def reflectPoints(input_points, refl_pt_a, refl_pt_b):
    try:
        m = (refl_pt_a.y() - refl_pt_b.y()) / (refl_pt_a.x() - refl_pt_b.x()) # get the slope of the reflection line
        b = refl_pt_a.y() - m * refl_pt_a.x() # get how 'shifted' the line is
        try:
            inv_m = -1 / m # get the slope of perpendicular line to the reflection line
            for x, y in input_points: # loop throw the list of points
                # create a perpendicular line that touches the n point of input_points
                b_n = y - inv_m * x # find how 'shifted' the perpendicular line is
                l_x_coord = (b_n - b) / (m - inv_m) # get the x coordinate of the intersection between the reflection line and the perpendicular line
                l_y_coord = m * l_x_coord + b # get the y coordinate of the intersection between the reflection line and the perpendicular line
                new_x = 2 * l_x_coord - x # different approach of the Midpoint theorem to find the x coordinate reflected
                new_y = 2 * l_y_coord - y # different approach of the Midpoint theorem to find the y coordinate reflected
                yield QgsPointXY(new_x, new_y)
        except ZeroDivisionError: # handle reflection across horizontal line (undefined slope)
            for x, y in input_points:
                l_y_coord = refl_pt_a.y()
                new_x = x
                new_y = 2 * l_y_coord - y
                yield QgsPointXY(new_x, new_y)
    except ZeroDivisionError: # handle reflection across vertical line (no slope)
        for x, y in input_points:
            l_x_coord = refl_pt_a.x()
            new_x = 2 * l_x_coord - x
            new_y = y
            yield QgsPointXY(new_x, new_y)
canvas = iface.mapCanvas()
map_tool = QgsMapToolCapture(canvas, iface.cadDockWidget(), QgsMapToolCapture.CaptureLine)
map_tool.setAutoSnapEnabled(True)
map_tool.startCapturing()
canvas.setMapTool(map_tool)
old_point = False # this variable will store the first point of the reflection line
def getPoints(mouse):
    point = mouse.mapPoint()
    button = mouse.button()
    global old_point
    if not old_point and button == 1:
        old_point = QgsPointXY(point)
    elif old_point and button == 2:
        input_layer = iface.activeLayer() # get active layer
        feature = next(input_layer.getSelectedFeatures()) # get the selected layer, here it only takes one feature if more than one where selected
        input_points = feature.geometry().asPolygon()[0] # get the geometry of only one polygon if multipolygon geometry
        output_points = list(reflectPoints(input_points, point, old_point)) # reflect the vertices
        input_layer.startEditing()
        geometry = QgsGeometry.fromPolygonXY([output_points])
        input_layer.changeGeometry(feature.id(), geometry)
        old_point = False
        input_layer.commitChanges()
        global map_tool
        global canvas
        canvas.unsetMapTool(map_tool)

map_tool.cadCanvasPressEvent = getPoints
0
3

You can use the "Geometry Generator" to dynamically create a copy (visualization only) of your polygon, flipped around the line on the layer axis using the following expression. Whenever you change the line, the flipped polygon will adapt immediately:

make_polygon(
    make_line (
        array_foreach (
            generate_series (1,  num_points( $geometry)),
            with_variable (
                'line',
                make_line (
                    point_n( $geometry, @element),
                    closest_point( 
                        extend (
                            overlay_nearest(
                                'axis',  --change the name to the name of the line layer you use
                                $geometry
                            )[0], 
                            100000, -- adapt these distances on this and the next line (just needed in case the axis is shorter then the polygon)
                            100000
                        ),
                        point_n ($geometry, @element)
                    )
                ),
                end_point (
                    extend (@line,0,length(@line))
                )
            )
        )
    )
)

Red: initial polygon, blue: flipped polygon; black: axis enter image description here

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.