I wrote this code that reflects a list of points 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.
Define function to reflect a list of QgsPointXY between a line defined by two points.
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)
Get the points to build the reflection line, I will define this function to capture the mouse when the map canvas is clicked.
canva = iface.mapCanvas()
map_tool = QgsMapToolEmitPoint(canva)
canva.setMapTool(map_tool)
old_point = False # this variable will store the first point of the reflection line
def getPoints(point, 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
map_tool.canvasClicked.connect(getPoints)
How to use it
Paste and run the shown scripts as a new python script in the QGIS python console, then select the feature to reflect, select with left click the first point of the reflection line and select the second point with right click and the feature will be reflected. Execute this line in the python console map_tool.canvasClicked.disconnect(getPoints)
for disabling the reflection tool.
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 doesn't include snapping tools.
Edit
Snapping enabled. 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