QGIS/python/plugins/processing/algs/qgis/RectanglesOvalsDiamondsVariable.py
2024-11-29 15:38:02 +01:00

466 lines
16 KiB
Python

"""
***************************************************************************
RectanglesOvalsDiamondsVariable.py
---------------------
Date : April 2016
Copyright : (C) 2016 by Alexander Bruy
Email : alexander dot bruy at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = "Alexander Bruy"
__date__ = "August 2012"
__copyright__ = "(C) 2012, Victor Olaya"
import math
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (
NULL,
QgsWkbTypes,
QgsFeature,
QgsFeatureSink,
QgsGeometry,
QgsPointXY,
QgsProcessing,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterEnum,
QgsProcessingParameterNumber,
QgsProcessingParameterFeatureSink,
)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
class RectanglesOvalsDiamondsVariable(QgisAlgorithm):
INPUT = "INPUT"
SHAPE = "SHAPE"
WIDTH = "WIDTH"
HEIGHT = "HEIGHT"
ROTATION = "ROTATION"
SEGMENTS = "SEGMENTS"
OUTPUT = "OUTPUT"
def group(self):
return self.tr("Vector geometry")
def groupId(self):
return "vectorgeometry"
def __init__(self):
super().__init__()
def flags(self):
return super().flags() | QgsProcessingAlgorithm.Flag.FlagDeprecated
def initAlgorithm(self, config=None):
self.shapes = [self.tr("Rectangles"), self.tr("Diamonds"), self.tr("Ovals")]
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr("Input layer"),
[QgsProcessing.SourceType.TypeVectorPoint],
)
)
self.addParameter(
QgsProcessingParameterEnum(
self.SHAPE, self.tr("Buffer shape"), options=self.shapes
)
)
self.addParameter(
QgsProcessingParameterField(
self.WIDTH,
self.tr("Width field"),
parentLayerParameterName=self.INPUT,
type=QgsProcessingParameterField.DataType.Numeric,
)
)
self.addParameter(
QgsProcessingParameterField(
self.HEIGHT,
self.tr("Height field"),
parentLayerParameterName=self.INPUT,
type=QgsProcessingParameterField.DataType.Numeric,
)
)
self.addParameter(
QgsProcessingParameterField(
self.ROTATION,
self.tr("Rotation field"),
parentLayerParameterName=self.INPUT,
type=QgsProcessingParameterField.DataType.Numeric,
optional=True,
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.SEGMENTS,
self.tr("Number of segments"),
minValue=1,
defaultValue=36,
)
)
self.addParameter(
QgsProcessingParameterFeatureSink(
self.OUTPUT,
self.tr("Output"),
type=QgsProcessing.SourceType.TypeVectorPolygon,
)
)
def name(self):
return "rectanglesovalsdiamondsvariable"
def displayName(self):
return self.tr("Rectangles, ovals, diamonds (variable)")
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
if source is None:
raise QgsProcessingException(
self.invalidSourceError(parameters, self.INPUT)
)
shape = self.parameterAsEnum(parameters, self.SHAPE, context)
width_field = self.parameterAsString(parameters, self.WIDTH, context)
height_field = self.parameterAsString(parameters, self.HEIGHT, context)
rotation_field = self.parameterAsString(parameters, self.ROTATION, context)
segments = self.parameterAsInt(parameters, self.SEGMENTS, context)
(sink, dest_id) = self.parameterAsSink(
parameters,
self.OUTPUT,
context,
source.fields(),
QgsWkbTypes.Type.Polygon,
source.sourceCrs(),
)
if sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
width = source.fields().lookupField(width_field)
height = source.fields().lookupField(height_field)
rotation = source.fields().lookupField(rotation_field)
if shape == 0:
self.rectangles(sink, source, width, height, rotation, feedback)
elif shape == 1:
self.diamonds(sink, source, width, height, rotation, feedback)
else:
self.ovals(sink, source, width, height, rotation, segments, feedback)
sink.finalize()
return {self.OUTPUT: dest_id}
def rectangles(self, sink, source, width, height, rotation, feedback):
ft = QgsFeature()
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
if rotation >= 0:
for current, feat in enumerate(features):
if feedback.isCanceled():
break
if not feat.hasGeometry():
continue
w = feat[width]
h = feat[height]
angle = feat[rotation]
# block 0/NULL width or height, but allow 0 as angle value
if not w or not h:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "width or height. " "Skipping…",
).format(feat.id())
)
continue
if angle is NULL:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "angle. " "Skipping…",
).format(feat.id())
)
continue
xOffset = w / 2.0
yOffset = h / 2.0
phi = angle * math.pi / 180
point = feat.geometry().asPoint()
x = point.x()
y = point.y()
points = [
(-xOffset, -yOffset),
(-xOffset, yOffset),
(xOffset, yOffset),
(xOffset, -yOffset),
]
polygon = [
[
QgsPointXY(
i[0] * math.cos(phi) + i[1] * math.sin(phi) + x,
-i[0] * math.sin(phi) + i[1] * math.cos(phi) + y,
)
for i in points
]
]
ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
ft.setAttributes(feat.attributes())
sink.addFeature(ft, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))
else:
for current, feat in enumerate(features):
if feedback.isCanceled():
break
if not feat.hasGeometry():
continue
w = feat[width]
h = feat[height]
if not w or not h:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "width or height. " "Skipping…",
).format(feat.id())
)
continue
xOffset = w / 2.0
yOffset = h / 2.0
point = feat.geometry().asPoint()
x = point.x()
y = point.y()
points = [
(-xOffset, -yOffset),
(-xOffset, yOffset),
(xOffset, yOffset),
(xOffset, -yOffset),
]
polygon = [[QgsPointXY(i[0] + x, i[1] + y) for i in points]]
ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
ft.setAttributes(feat.attributes())
sink.addFeature(ft, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))
def diamonds(self, sink, source, width, height, rotation, feedback):
features = source.getFeatures()
ft = QgsFeature()
total = 100.0 / source.featureCount() if source.featureCount() else 0
if rotation >= 0:
for current, feat in enumerate(features):
if feedback.isCanceled():
break
if not feat.hasGeometry():
continue
w = feat[width]
h = feat[height]
angle = feat[rotation]
# block 0/NULL width or height, but allow 0 as angle value
if not w or not h:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "width or height. " "Skipping…",
).format(feat.id())
)
continue
if angle is NULL:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "angle. " "Skipping…",
).format(feat.id())
)
continue
xOffset = w / 2.0
yOffset = h / 2.0
phi = angle * math.pi / 180
point = feat.geometry().asPoint()
x = point.x()
y = point.y()
points = [
(0.0, -yOffset),
(-xOffset, 0.0),
(0.0, yOffset),
(xOffset, 0.0),
]
polygon = [
[
QgsPointXY(
i[0] * math.cos(phi) + i[1] * math.sin(phi) + x,
-i[0] * math.sin(phi) + i[1] * math.cos(phi) + y,
)
for i in points
]
]
ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
ft.setAttributes(feat.attributes())
sink.addFeature(ft, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))
else:
for current, feat in enumerate(features):
if feedback.isCanceled():
break
if not feat.hasGeometry():
continue
w = feat[width]
h = feat[height]
if not w or not h:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "width or height. " "Skipping…",
).format(feat.id())
)
continue
xOffset = w / 2.0
yOffset = h / 2.0
point = feat.geometry().asPoint()
x = point.x()
y = point.y()
points = [
(0.0, -yOffset),
(-xOffset, 0.0),
(0.0, yOffset),
(xOffset, 0.0),
]
polygon = [[QgsPointXY(i[0] + x, i[1] + y) for i in points]]
ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
ft.setAttributes(feat.attributes())
sink.addFeature(ft, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))
def ovals(self, sink, source, width, height, rotation, segments, feedback):
features = source.getFeatures()
ft = QgsFeature()
total = 100.0 / source.featureCount() if source.featureCount() else 0
if rotation >= 0:
for current, feat in enumerate(features):
if feedback.isCanceled():
break
if not feat.hasGeometry():
continue
w = feat[width]
h = feat[height]
angle = feat[rotation]
# block 0/NULL width or height, but allow 0 as angle value
if not w or not h:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "width or height. " "Skipping…",
).format(feat.id())
)
continue
if angle == NULL:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "angle. " "Skipping…",
).format(feat.id())
)
continue
xOffset = w / 2.0
yOffset = h / 2.0
phi = angle * math.pi / 180
point = feat.geometry().asPoint()
x = point.x()
y = point.y()
points = [
(xOffset * math.cos(t), yOffset * math.sin(t))
for t in [(2 * math.pi) / segments * i for i in range(segments)]
]
polygon = [
[
QgsPointXY(
i[0] * math.cos(phi) + i[1] * math.sin(phi) + x,
-i[0] * math.sin(phi) + i[1] * math.cos(phi) + y,
)
for i in points
]
]
ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
ft.setAttributes(feat.attributes())
sink.addFeature(ft, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))
else:
for current, feat in enumerate(features):
if feedback.isCanceled():
break
if not feat.hasGeometry():
continue
w = feat[width]
h = feat[height]
if not w or not h:
feedback.pushInfo(
QCoreApplication.translate(
"RectanglesOvalsDiamondsVariable",
"Feature {} has empty " "width or height. " "Skipping…",
).format(feat.id())
)
continue
xOffset = w / 2.0
yOffset = h / 2.0
point = feat.geometry().asPoint()
x = point.x()
y = point.y()
points = [
(xOffset * math.cos(t), yOffset * math.sin(t))
for t in [(2 * math.pi) / segments * i for i in range(segments)]
]
polygon = [[QgsPointXY(i[0] + x, i[1] + y) for i in points]]
ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
ft.setAttributes(feat.attributes())
sink.addFeature(ft, QgsFeatureSink.Flag.FastInsert)
feedback.setProgress(int(current * total))