RectanglesOvalsDiamondsVariable.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. """
  2. ***************************************************************************
  3. RectanglesOvalsDiamondsVariable.py
  4. ---------------------
  5. Date : April 2016
  6. Copyright : (C) 2016 by Alexander Bruy
  7. Email : alexander dot bruy at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Alexander Bruy'
  18. __date__ = 'August 2012'
  19. __copyright__ = '(C) 2012, Victor Olaya'
  20. import math
  21. from qgis.PyQt.QtCore import QCoreApplication
  22. from qgis.core import (NULL,
  23. QgsWkbTypes,
  24. QgsFeature,
  25. QgsFeatureSink,
  26. QgsGeometry,
  27. QgsPointXY,
  28. QgsProcessing,
  29. QgsProcessingException,
  30. QgsProcessingAlgorithm,
  31. QgsProcessingParameterField,
  32. QgsProcessingParameterFeatureSource,
  33. QgsProcessingParameterEnum,
  34. QgsProcessingParameterNumber,
  35. QgsProcessingParameterFeatureSink)
  36. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  37. class RectanglesOvalsDiamondsVariable(QgisAlgorithm):
  38. INPUT = 'INPUT'
  39. SHAPE = 'SHAPE'
  40. WIDTH = 'WIDTH'
  41. HEIGHT = 'HEIGHT'
  42. ROTATION = 'ROTATION'
  43. SEGMENTS = 'SEGMENTS'
  44. OUTPUT = 'OUTPUT'
  45. def group(self):
  46. return self.tr('Vector geometry')
  47. def groupId(self):
  48. return 'vectorgeometry'
  49. def __init__(self):
  50. super().__init__()
  51. def flags(self):
  52. return super().flags() | QgsProcessingAlgorithm.FlagDeprecated
  53. def initAlgorithm(self, config=None):
  54. self.shapes = [self.tr('Rectangles'), self.tr('Diamonds'), self.tr('Ovals')]
  55. self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
  56. self.tr('Input layer'),
  57. [QgsProcessing.TypeVectorPoint]))
  58. self.addParameter(QgsProcessingParameterEnum(self.SHAPE,
  59. self.tr('Buffer shape'), options=self.shapes))
  60. self.addParameter(QgsProcessingParameterField(self.WIDTH,
  61. self.tr('Width field'),
  62. parentLayerParameterName=self.INPUT,
  63. type=QgsProcessingParameterField.Numeric))
  64. self.addParameter(QgsProcessingParameterField(self.HEIGHT,
  65. self.tr('Height field'),
  66. parentLayerParameterName=self.INPUT,
  67. type=QgsProcessingParameterField.Numeric))
  68. self.addParameter(QgsProcessingParameterField(self.ROTATION,
  69. self.tr('Rotation field'),
  70. parentLayerParameterName=self.INPUT,
  71. type=QgsProcessingParameterField.Numeric,
  72. optional=True))
  73. self.addParameter(QgsProcessingParameterNumber(self.SEGMENTS,
  74. self.tr('Number of segments'),
  75. minValue=1,
  76. defaultValue=36))
  77. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
  78. self.tr('Output'),
  79. type=QgsProcessing.TypeVectorPolygon))
  80. def name(self):
  81. return 'rectanglesovalsdiamondsvariable'
  82. def displayName(self):
  83. return self.tr('Rectangles, ovals, diamonds (variable)')
  84. def processAlgorithm(self, parameters, context, feedback):
  85. source = self.parameterAsSource(parameters, self.INPUT, context)
  86. if source is None:
  87. raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
  88. shape = self.parameterAsEnum(parameters, self.SHAPE, context)
  89. width_field = self.parameterAsString(parameters, self.WIDTH, context)
  90. height_field = self.parameterAsString(parameters, self.HEIGHT, context)
  91. rotation_field = self.parameterAsString(parameters, self.ROTATION, context)
  92. segments = self.parameterAsInt(parameters, self.SEGMENTS, context)
  93. (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
  94. source.fields(), QgsWkbTypes.Polygon, source.sourceCrs())
  95. if sink is None:
  96. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  97. width = source.fields().lookupField(width_field)
  98. height = source.fields().lookupField(height_field)
  99. rotation = source.fields().lookupField(rotation_field)
  100. if shape == 0:
  101. self.rectangles(sink, source, width, height, rotation, feedback)
  102. elif shape == 1:
  103. self.diamonds(sink, source, width, height, rotation, feedback)
  104. else:
  105. self.ovals(sink, source, width, height, rotation, segments, feedback)
  106. return {self.OUTPUT: dest_id}
  107. def rectangles(self, sink, source, width, height, rotation, feedback):
  108. ft = QgsFeature()
  109. features = source.getFeatures()
  110. total = 100.0 / source.featureCount() if source.featureCount() else 0
  111. if rotation >= 0:
  112. for current, feat in enumerate(features):
  113. if feedback.isCanceled():
  114. break
  115. if not feat.hasGeometry():
  116. continue
  117. w = feat[width]
  118. h = feat[height]
  119. angle = feat[rotation]
  120. # block 0/NULL width or height, but allow 0 as angle value
  121. if not w or not h:
  122. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  123. 'width or height. '
  124. 'Skipping…').format(feat.id()))
  125. continue
  126. if angle is NULL:
  127. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  128. 'angle. '
  129. 'Skipping…').format(feat.id()))
  130. continue
  131. xOffset = w / 2.0
  132. yOffset = h / 2.0
  133. phi = angle * math.pi / 180
  134. point = feat.geometry().asPoint()
  135. x = point.x()
  136. y = point.y()
  137. points = [(-xOffset, -yOffset), (-xOffset, yOffset), (xOffset, yOffset), (xOffset, -yOffset)]
  138. polygon = [[QgsPointXY(i[0] * math.cos(phi) + i[1] * math.sin(phi) + x,
  139. -i[0] * math.sin(phi) + i[1] * math.cos(phi) + y) for i in points]]
  140. ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
  141. ft.setAttributes(feat.attributes())
  142. sink.addFeature(ft, QgsFeatureSink.FastInsert)
  143. feedback.setProgress(int(current * total))
  144. else:
  145. for current, feat in enumerate(features):
  146. if feedback.isCanceled():
  147. break
  148. if not feat.hasGeometry():
  149. continue
  150. w = feat[width]
  151. h = feat[height]
  152. if not w or not h:
  153. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  154. 'width or height. '
  155. 'Skipping…').format(feat.id()))
  156. continue
  157. xOffset = w / 2.0
  158. yOffset = h / 2.0
  159. point = feat.geometry().asPoint()
  160. x = point.x()
  161. y = point.y()
  162. points = [(-xOffset, -yOffset), (-xOffset, yOffset), (xOffset, yOffset), (xOffset, -yOffset)]
  163. polygon = [[QgsPointXY(i[0] + x, i[1] + y) for i in points]]
  164. ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
  165. ft.setAttributes(feat.attributes())
  166. sink.addFeature(ft, QgsFeatureSink.FastInsert)
  167. feedback.setProgress(int(current * total))
  168. def diamonds(self, sink, source, width, height, rotation, feedback):
  169. features = source.getFeatures()
  170. ft = QgsFeature()
  171. total = 100.0 / source.featureCount() if source.featureCount() else 0
  172. if rotation >= 0:
  173. for current, feat in enumerate(features):
  174. if feedback.isCanceled():
  175. break
  176. if not feat.hasGeometry():
  177. continue
  178. w = feat[width]
  179. h = feat[height]
  180. angle = feat[rotation]
  181. # block 0/NULL width or height, but allow 0 as angle value
  182. if not w or not h:
  183. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  184. 'width or height. '
  185. 'Skipping…').format(feat.id()))
  186. continue
  187. if angle is NULL:
  188. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  189. 'angle. '
  190. 'Skipping…').format(feat.id()))
  191. continue
  192. xOffset = w / 2.0
  193. yOffset = h / 2.0
  194. phi = angle * math.pi / 180
  195. point = feat.geometry().asPoint()
  196. x = point.x()
  197. y = point.y()
  198. points = [(0.0, -yOffset), (-xOffset, 0.0), (0.0, yOffset), (xOffset, 0.0)]
  199. polygon = [[QgsPointXY(i[0] * math.cos(phi) + i[1] * math.sin(phi) + x,
  200. -i[0] * math.sin(phi) + i[1] * math.cos(phi) + y) for i in points]]
  201. ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
  202. ft.setAttributes(feat.attributes())
  203. sink.addFeature(ft, QgsFeatureSink.FastInsert)
  204. feedback.setProgress(int(current * total))
  205. else:
  206. for current, feat in enumerate(features):
  207. if feedback.isCanceled():
  208. break
  209. if not feat.hasGeometry():
  210. continue
  211. w = feat[width]
  212. h = feat[height]
  213. if not w or not h:
  214. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  215. 'width or height. '
  216. 'Skipping…').format(feat.id()))
  217. continue
  218. xOffset = w / 2.0
  219. yOffset = h / 2.0
  220. point = feat.geometry().asPoint()
  221. x = point.x()
  222. y = point.y()
  223. points = [(0.0, -yOffset), (-xOffset, 0.0), (0.0, yOffset), (xOffset, 0.0)]
  224. polygon = [[QgsPointXY(i[0] + x, i[1] + y) for i in points]]
  225. ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
  226. ft.setAttributes(feat.attributes())
  227. sink.addFeature(ft, QgsFeatureSink.FastInsert)
  228. feedback.setProgress(int(current * total))
  229. def ovals(self, sink, source, width, height, rotation, segments, feedback):
  230. features = source.getFeatures()
  231. ft = QgsFeature()
  232. total = 100.0 / source.featureCount() if source.featureCount() else 0
  233. if rotation >= 0:
  234. for current, feat in enumerate(features):
  235. if feedback.isCanceled():
  236. break
  237. if not feat.hasGeometry():
  238. continue
  239. w = feat[width]
  240. h = feat[height]
  241. angle = feat[rotation]
  242. # block 0/NULL width or height, but allow 0 as angle value
  243. if not w or not h:
  244. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  245. 'width or height. '
  246. 'Skipping…').format(feat.id()))
  247. continue
  248. if angle == NULL:
  249. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  250. 'angle. '
  251. 'Skipping…').format(feat.id()))
  252. continue
  253. xOffset = w / 2.0
  254. yOffset = h / 2.0
  255. phi = angle * math.pi / 180
  256. point = feat.geometry().asPoint()
  257. x = point.x()
  258. y = point.y()
  259. points = [
  260. (xOffset * math.cos(t), yOffset * math.sin(t))
  261. for t in [(2 * math.pi) / segments * i for i in range(segments)]
  262. ]
  263. polygon = [[QgsPointXY(i[0] * math.cos(phi) + i[1] * math.sin(phi) + x,
  264. -i[0] * math.sin(phi) + i[1] * math.cos(phi) + y) for i in points]]
  265. ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
  266. ft.setAttributes(feat.attributes())
  267. sink.addFeature(ft, QgsFeatureSink.FastInsert)
  268. feedback.setProgress(int(current * total))
  269. else:
  270. for current, feat in enumerate(features):
  271. if feedback.isCanceled():
  272. break
  273. if not feat.hasGeometry():
  274. continue
  275. w = feat[width]
  276. h = feat[height]
  277. if not w or not h:
  278. feedback.pushInfo(QCoreApplication.translate('RectanglesOvalsDiamondsVariable', 'Feature {} has empty '
  279. 'width or height. '
  280. 'Skipping…').format(feat.id()))
  281. continue
  282. xOffset = w / 2.0
  283. yOffset = h / 2.0
  284. point = feat.geometry().asPoint()
  285. x = point.x()
  286. y = point.y()
  287. points = [
  288. (xOffset * math.cos(t), yOffset * math.sin(t))
  289. for t in [(2 * math.pi) / segments * i for i in range(segments)]
  290. ]
  291. polygon = [[QgsPointXY(i[0] + x, i[1] + y) for i in points]]
  292. ft.setGeometry(QgsGeometry.fromPolygonXY(polygon))
  293. ft.setAttributes(feat.attributes())
  294. sink.addFeature(ft, QgsFeatureSink.FastInsert)
  295. feedback.setProgress(int(current * total))