FieldPyculator.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. """
  2. ***************************************************************************
  3. FieldPyculator.py
  4. ---------------------
  5. Date : August 2012
  6. Copyright : (C) 2012 by Victor Olaya
  7. Email : volayaf 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__ = 'Victor Olaya & NextGIS'
  18. __date__ = 'August 2012'
  19. __copyright__ = '(C) 2012, Victor Olaya & NextGIS'
  20. import sys
  21. from qgis.PyQt.QtCore import QVariant
  22. from qgis.core import (QgsProcessingException,
  23. QgsField,
  24. QgsFields,
  25. QgsFeatureSink,
  26. QgsProcessing,
  27. QgsProcessingParameterFeatureSource,
  28. QgsProcessingParameterString,
  29. QgsProcessingParameterEnum,
  30. QgsProcessingParameterNumber,
  31. QgsProcessingParameterFeatureSink,
  32. QgsVariantUtils)
  33. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  34. class FieldsPyculator(QgisAlgorithm):
  35. INPUT = 'INPUT'
  36. FIELD_NAME = 'FIELD_NAME'
  37. FIELD_TYPE = 'FIELD_TYPE'
  38. FIELD_LENGTH = 'FIELD_LENGTH'
  39. FIELD_PRECISION = 'FIELD_PRECISION'
  40. GLOBAL = 'GLOBAL'
  41. FORMULA = 'FORMULA'
  42. OUTPUT = 'OUTPUT'
  43. RESULT_VAR_NAME = 'value'
  44. TYPES = [QVariant.LongLong, QVariant.Double, QVariant.String]
  45. def group(self):
  46. return self.tr('Vector table')
  47. def groupId(self):
  48. return 'vectortable'
  49. def __init__(self):
  50. super().__init__()
  51. def initAlgorithm(self, config=None):
  52. self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'),
  53. types=[QgsProcessing.TypeVector]))
  54. self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
  55. self.tr('Result field name'), defaultValue='NewField'))
  56. types = [(QVariant.Int, QVariant.Invalid),
  57. (QVariant.Double, QVariant.Invalid),
  58. (QVariant.String, QVariant.Invalid),
  59. (QVariant.Bool, QVariant.Invalid),
  60. (QVariant.Date, QVariant.Invalid),
  61. (QVariant.Time, QVariant.Invalid),
  62. (QVariant.DateTime, QVariant.Invalid),
  63. (QVariant.ByteArray, QVariant.Invalid),
  64. (QVariant.StringList, QVariant.String),
  65. (QVariant.List, QVariant.Int),
  66. (QVariant.List, QVariant.Double)]
  67. type_names = []
  68. type_icons = []
  69. for type_name, subtype_name in types:
  70. type_names.append(QgsVariantUtils.typeToDisplayString(type_name, subtype_name))
  71. type_icons.append(QgsFields.iconForFieldType(type_name, subtype_name))
  72. param = QgsProcessingParameterEnum('FIELD_TYPE', 'Field type', options=type_names)
  73. param.setMetadata({'widget_wrapper': {'icons': type_icons}})
  74. self.addParameter(param)
  75. self.addParameter(QgsProcessingParameterNumber(self.FIELD_LENGTH,
  76. self.tr('Field length'), minValue=0,
  77. defaultValue=10))
  78. self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION,
  79. self.tr('Field precision'), minValue=0, maxValue=15,
  80. defaultValue=3))
  81. self.addParameter(QgsProcessingParameterString(self.GLOBAL,
  82. self.tr('Global expression'), multiLine=True, optional=True))
  83. self.addParameter(QgsProcessingParameterString(self.FORMULA,
  84. self.tr('Formula'), defaultValue='value = ', multiLine=True))
  85. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
  86. self.tr('Calculated')))
  87. def name(self):
  88. return 'advancedpythonfieldcalculator'
  89. def displayName(self):
  90. return self.tr('Advanced Python field calculator')
  91. def processAlgorithm(self, parameters, context, feedback):
  92. source = self.parameterAsSource(parameters, self.INPUT, context)
  93. if source is None:
  94. raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
  95. field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
  96. field_type = QVariant.Invalid
  97. field_sub_type = QVariant.Invalid
  98. field_type_parameter = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
  99. if field_type_parameter == 0: # Integer
  100. field_type = QVariant.Int
  101. elif field_type_parameter == 1: # Float
  102. field_type = QVariant.Double
  103. elif field_type_parameter == 2: # String
  104. field_type = QVariant.String
  105. elif field_type_parameter == 3: # Boolean
  106. field_type = QVariant.Bool
  107. elif field_type_parameter == 4: # Date
  108. field_type = QVariant.Date
  109. elif field_type_parameter == 5: # Time
  110. field_type = QVariant.Time
  111. elif field_type_parameter == 6: # DateTime
  112. field_type = QVariant.DateTime
  113. elif field_type_parameter == 7: # Binary
  114. field_type = QVariant.ByteArray
  115. elif field_type_parameter == 8: # StringList
  116. field_type = QVariant.StringList
  117. field_sub_type = QVariant.String
  118. elif field_type_parameter == 9: # IntegerList
  119. field_type = QVariant.List
  120. field_sub_type = QVariant.Int
  121. elif field_type_parameter == 10: # DoubleList
  122. field_type = QVariant.List
  123. field_sub_type = QVariant.Double
  124. width = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
  125. precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
  126. code = self.parameterAsString(parameters, self.FORMULA, context)
  127. globalExpression = self.parameterAsString(parameters, self.GLOBAL, context)
  128. fields = source.fields()
  129. field = QgsField(field_name, field_type, '', width, precision, '', field_sub_type)
  130. fields.append(field)
  131. new_ns = {}
  132. (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
  133. fields, source.wkbType(), source.sourceCrs())
  134. if sink is None:
  135. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  136. # Run global code
  137. if globalExpression.strip() != '':
  138. try:
  139. bytecode = compile(globalExpression, '<string>', 'exec')
  140. exec(bytecode, new_ns)
  141. except:
  142. raise QgsProcessingException(
  143. self.tr("FieldPyculator code execute error. Global code block can't be executed!\n{0}\n{1}").format(
  144. str(sys.exc_info()[0].__name__), str(sys.exc_info()[1])))
  145. # Replace all fields tags
  146. fields = source.fields()
  147. for num, field in enumerate(fields):
  148. field_name = str(field.name())
  149. replval = '__attr[' + str(num) + ']'
  150. code = code.replace('<' + field_name + '>', replval)
  151. # Replace all special vars
  152. code = code.replace('$id', '__id')
  153. code = code.replace('$geom', '__geom')
  154. need_id = code.find('__id') != -1
  155. need_geom = code.find('__geom') != -1
  156. need_attrs = code.find('__attr') != -1
  157. # Compile
  158. try:
  159. bytecode = compile(code, '<string>', 'exec')
  160. except:
  161. raise QgsProcessingException(
  162. self.tr("FieldPyculator code execute error. Field code block can't be executed!\n{0}\n{1}").format(
  163. str(sys.exc_info()[0].__name__), str(sys.exc_info()[1])))
  164. # Run
  165. features = source.getFeatures()
  166. total = 100.0 / source.featureCount() if source.featureCount() else 0
  167. for current, feat in enumerate(features):
  168. if feedback.isCanceled():
  169. break
  170. feedback.setProgress(int(current * total))
  171. attrs = feat.attributes()
  172. feat_id = feat.id()
  173. # Add needed vars
  174. if need_id:
  175. new_ns['__id'] = feat_id
  176. if need_geom:
  177. geom = feat.geometry()
  178. new_ns['__geom'] = geom
  179. if need_attrs:
  180. pyattrs = [a for a in attrs]
  181. new_ns['__attr'] = pyattrs
  182. # Clear old result
  183. if self.RESULT_VAR_NAME in new_ns:
  184. del new_ns[self.RESULT_VAR_NAME]
  185. # Exec
  186. exec(bytecode, new_ns)
  187. # Check result
  188. if self.RESULT_VAR_NAME not in new_ns:
  189. raise QgsProcessingException(
  190. self.tr("FieldPyculator code execute error\n"
  191. "Field code block does not return '{0}' variable! "
  192. "Please declare this variable in your code!").format(self.RESULT_VAR_NAME))
  193. # Write feature
  194. attrs.append(new_ns[self.RESULT_VAR_NAME])
  195. feat.setAttributes(attrs)
  196. sink.addFeature(feat, QgsFeatureSink.FastInsert)
  197. return {self.OUTPUT: dest_id}
  198. def checkParameterValues(self, parameters, context):
  199. # TODO check that formula is correct and fields exist
  200. return super().checkParameterValues(parameters, context)