1# -*- coding: utf-8 -*-
2
3"""
4***************************************************************************
5    Dissolve.py
6    ---------------------
7    Date                 : Janaury 2015
8    Copyright            : (C) 2015 by Giovanni Manghi
9    Email                : giovanni dot manghi at naturalgis dot pt
10***************************************************************************
11*                                                                         *
12*   This program is free software; you can redistribute it and/or modify  *
13*   it under the terms of the GNU General Public License as published by  *
14*   the Free Software Foundation; either version 2 of the License, or     *
15*   (at your option) any later version.                                   *
16*                                                                         *
17***************************************************************************
18"""
19
20__author__ = 'Giovanni Manghi'
21__date__ = 'January 2015'
22__copyright__ = '(C) 2015, Giovanni Manghi'
23
24from qgis.core import (QgsProcessingException,
25                       QgsProcessingParameterDefinition,
26                       QgsProcessingParameterFeatureSource,
27                       QgsProcessingParameterField,
28                       QgsProcessingParameterString,
29                       QgsProcessingParameterBoolean,
30                       QgsProcessingParameterVectorDestination)
31from processing.algs.gdal.GdalAlgorithm import GdalAlgorithm
32from processing.algs.gdal.GdalUtils import GdalUtils
33
34
35class Dissolve(GdalAlgorithm):
36    INPUT = 'INPUT'
37    FIELD = 'FIELD'
38    GEOMETRY = 'GEOMETRY'
39    EXPLODE_COLLECTIONS = 'EXPLODE_COLLECTIONS'
40    KEEP_ATTRIBUTES = 'KEEP_ATTRIBUTES'
41    COUNT_FEATURES = 'COUNT_FEATURES'
42    COMPUTE_AREA = 'COMPUTE_AREA'
43    COMPUTE_STATISTICS = 'COMPUTE_STATISTICS'
44    STATISTICS_ATTRIBUTE = 'STATISTICS_ATTRIBUTE'
45    OPTIONS = 'OPTIONS'
46    OUTPUT = 'OUTPUT'
47
48    def __init__(self):
49        super().__init__()
50
51    def initAlgorithm(self, config=None):
52        self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
53                                                              self.tr('Input layer')))
54        self.addParameter(QgsProcessingParameterField(self.FIELD,
55                                                      self.tr('Dissolve field'),
56                                                      None,
57                                                      self.INPUT,
58                                                      QgsProcessingParameterField.Any, optional=True))
59        self.addParameter(QgsProcessingParameterString(self.GEOMETRY,
60                                                       self.tr('Geometry column name'),
61                                                       defaultValue='geometry'))
62        params = []
63        params.append(QgsProcessingParameterBoolean(self.EXPLODE_COLLECTIONS,
64                                                    self.tr('Produce one feature for each geometry in any kind of geometry collection in the source file'),
65                                                    defaultValue=False))
66        params.append(QgsProcessingParameterBoolean(self.KEEP_ATTRIBUTES,
67                                                    self.tr('Keep input attributes'),
68                                                    defaultValue=False))
69        params.append(QgsProcessingParameterBoolean(self.COUNT_FEATURES,
70                                                    self.tr('Count dissolved features'),
71                                                    defaultValue=False))
72        params.append(QgsProcessingParameterBoolean(self.COMPUTE_AREA,
73                                                    self.tr('Compute area and perimeter of dissolved features'),
74                                                    defaultValue=False))
75        params.append(QgsProcessingParameterBoolean(self.COMPUTE_STATISTICS,
76                                                    self.tr('Compute min/max/sum/mean for attribute'),
77                                                    defaultValue=False))
78        params.append(QgsProcessingParameterField(self.STATISTICS_ATTRIBUTE,
79                                                  self.tr('Numeric attribute to calculate statistics on'),
80                                                  None,
81                                                  self.INPUT,
82                                                  QgsProcessingParameterField.Numeric,
83                                                  optional=True))
84        params.append(QgsProcessingParameterString(self.OPTIONS,
85                                                   self.tr('Additional creation options'),
86                                                   defaultValue='',
87                                                   optional=True))
88        for param in params:
89            param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
90            self.addParameter(param)
91
92        self.addParameter(QgsProcessingParameterVectorDestination(self.OUTPUT,
93                                                                  self.tr('Dissolved')))
94
95    def name(self):
96        return 'dissolve'
97
98    def displayName(self):
99        return self.tr('Dissolve')
100
101    def group(self):
102        return self.tr('Vector geoprocessing')
103
104    def groupId(self):
105        return 'vectorgeoprocessing'
106
107    def commandName(self):
108        return 'ogr2ogr'
109
110    def getConsoleCommands(self, parameters, context, feedback, executing=True):
111        source = self.parameterAsSource(parameters, self.INPUT, context)
112        if source is None:
113            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
114
115        fields = source.fields()
116        ogrLayer, layerName = self.getOgrCompatibleSource(self.INPUT, parameters, context, feedback, executing)
117        geometry = self.parameterAsString(parameters, self.GEOMETRY, context)
118        fieldName = self.parameterAsString(parameters, self.FIELD, context)
119
120        options = self.parameterAsString(parameters, self.OPTIONS, context)
121        outFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)
122        self.setOutputValue(self.OUTPUT, outFile)
123
124        output, outputFormat = GdalUtils.ogrConnectionStringAndFormat(outFile, context)
125
126        other_fields = []
127        for f in fields:
128            if f.name() == geometry:
129                continue
130
131            other_fields.append('"{}"'.format(f.name()))
132
133        if other_fields:
134            other_fields = ',*'
135        else:
136            other_fields = ''
137
138        arguments = []
139        arguments.append(output)
140        arguments.append(ogrLayer)
141        arguments.append('-nlt PROMOTE_TO_MULTI')
142        arguments.append('-dialect')
143        arguments.append('sqlite')
144        arguments.append('-sql')
145
146        tokens = []
147        if self.parameterAsBoolean(parameters, self.COUNT_FEATURES, context):
148            tokens.append('COUNT({}) AS count'.format(geometry))
149
150        if self.parameterAsBoolean(parameters, self.COMPUTE_AREA, context):
151            tokens.append('SUM(ST_Area({0})) AS area, ST_Perimeter(ST_Union({0})) AS perimeter'.format(geometry))
152
153        statsField = self.parameterAsString(parameters, self.STATISTICS_ATTRIBUTE, context)
154        if statsField and self.parameterAsBoolean(parameters, self.COMPUTE_STATISTICS, context):
155            tokens.append('SUM("{0}") AS sum, MIN("{0}") AS min, MAX("{0}") AS max, AVG("{0}") AS avg'.format(statsField))
156
157        params = ','.join(tokens)
158        if params:
159            params = ', ' + params
160
161        group_by = ''
162        if fieldName:
163            group_by = ' GROUP BY "{}"'.format(fieldName)
164
165        if self.parameterAsBoolean(parameters, self.KEEP_ATTRIBUTES, context):
166            sql = 'SELECT ST_Union({}) AS {}{}{} FROM "{}"{}'.format(geometry, geometry, other_fields, params, layerName, group_by)
167        else:
168            sql = 'SELECT ST_Union({}) AS {}{}{} FROM "{}"{}'.format(geometry, geometry, ', "{}"'.format(fieldName) if fieldName else '',
169                                                                     params, layerName, group_by)
170
171        arguments.append(sql)
172
173        if self.parameterAsBoolean(parameters, self.EXPLODE_COLLECTIONS, context):
174            arguments.append('-explodecollections')
175
176        if options:
177            arguments.append(options)
178
179        if outputFormat:
180            arguments.append('-f {}'.format(outputFormat))
181
182        return [self.commandName(), GdalUtils.escapeAndJoin(arguments)]
183