27
27
28
28
import os
29
29
import operator
30
+ from enum import Enum
31
+
30
32
from collections import defaultdict , deque
31
33
32
34
from qgis .core import (QgsField ,
37
39
from qgis .PyQt .QtCore import (QVariant )
38
40
39
41
from processing .core .GeoAlgorithm import GeoAlgorithm
40
- from processing .core .parameters import ParameterVector
41
- from processing .core .parameters import ParameterNumber
42
+ from processing .core .parameters import (ParameterVector ,
43
+ ParameterSelection ,
44
+ ParameterNumber )
42
45
from processing .core .outputs import OutputVector
43
46
from processing .tools import dataobjects , vector
44
47
45
48
pluginPath = os .path .split (os .path .split (os .path .dirname (__file__ ))[0 ])[0 ]
46
49
50
+ class BalanceMethod (Enum ):
51
+ BY_COUNT = 0
52
+ BY_AREA = 1
53
+ BY_DISTANCE = 2
47
54
48
55
class TopoColor (GeoAlgorithm ):
49
56
INPUT_LAYER = 'INPUT_LAYER'
50
57
MIN_COLORS = 'MIN_COLORS'
58
+ BALANCE = 'BALANCE'
51
59
OUTPUT_LAYER = 'OUTPUT_LAYER'
52
60
61
+
53
62
def defineCharacteristics (self ):
54
63
self .name , self .i18n_name = self .trAlgorithm ('Topological coloring' )
55
64
self .group , self .i18n_group = self .trAlgorithm ('Cartographic tools' )
@@ -59,13 +68,21 @@ def defineCharacteristics(self):
59
68
self .tr ('Input layer' ), [dataobjects .TYPE_VECTOR_POLYGON ]))
60
69
self .addParameter (ParameterNumber (self .MIN_COLORS ,
61
70
self .tr ('Minimum number of colors' ), 1 , 1000 , 4 ))
71
+ balance_by = [self .tr ('By feature count' ),
72
+ self .tr ('By assigned area' ),
73
+ self .tr ('By distance between colors' )]
74
+ self .addParameter (ParameterSelection (
75
+ self .BALANCE ,
76
+ self .tr ('Balance color assignment' ),
77
+ balance_by ))
62
78
63
79
self .addOutput (OutputVector (self .OUTPUT_LAYER , self .tr ('Colored' ), datatype = [dataobjects .TYPE_VECTOR_POLYGON ]))
64
80
65
81
def processAlgorithm (self , feedback ):
66
82
layer = dataobjects .getObjectFromUri (
67
83
self .getParameterValue (self .INPUT_LAYER ))
68
84
min_colors = self .getParameterValue (self .MIN_COLORS )
85
+ balance_by = BalanceMethod (self .getParameterValue (self .BALANCE ))
69
86
70
87
fields = layer .fields ()
71
88
fields .append (QgsField ('color_id' , QVariant .Int ))
@@ -76,26 +93,25 @@ def processAlgorithm(self, feedback):
76
93
layer .wkbType (),
77
94
layer .crs ())
78
95
79
- # use a deque so we can drop features as we write them
80
- # it's a bit friendlier on memory usage
81
- features = deque (f for f in vector .features (layer ))
96
+ features = {f .id ():f for f in vector .features (layer )}
82
97
83
98
topology , id_graph = self .compute_graph (features , feedback )
84
- feature_colors = ColoringAlgorithm .balanced (topology ,
85
- feedback ,
99
+ feature_colors = ColoringAlgorithm .balanced (features ,
100
+ balance = balance_by ,
101
+ graph = topology ,
102
+ feedback = feedback ,
86
103
min_colors = min_colors )
87
104
88
105
max_colors = max (feature_colors .values ())
89
106
feedback .pushInfo (self .tr ('{} colors required' ).format (max_colors ))
90
107
91
108
total = 20.0 / len (features )
92
109
current = 0
93
- while features :
94
- input_feature = features .popleft ()
110
+ for feature_id , input_feature in features .items ():
95
111
output_feature = input_feature
96
112
attributes = input_feature .attributes ()
97
- if input_feature . id () in feature_colors :
98
- attributes .append (feature_colors [input_feature . id () ])
113
+ if feature_id in feature_colors :
114
+ attributes .append (feature_colors [feature_id ])
99
115
else :
100
116
attributes .append (NULL )
101
117
output_feature .setAttributes (attributes )
@@ -115,7 +131,7 @@ def compute_graph(features, feedback, create_id_graph=False):
115
131
id_graph = Graph (sort_graph = True )
116
132
117
133
# skip features without geometry
118
- features_with_geometry = dict (( f . id () , f ) for f in features if f .hasGeometry ())
134
+ features_with_geometry = { f_id : f for ( f_id , f ) in features . items () if f .hasGeometry () }
119
135
120
136
total = 70.0 / len (features_with_geometry )
121
137
index = QgsSpatialIndex ()
@@ -151,7 +167,7 @@ def compute_graph(features, feedback, create_id_graph=False):
151
167
class ColoringAlgorithm :
152
168
153
169
@staticmethod
154
- def balanced (graph , feedback , min_colors = 4 ):
170
+ def balanced (features , graph , feedback , balance = BalanceMethod . BY_COUNT , min_colors = 4 ):
155
171
feature_colors = {}
156
172
# start with minimum number of colors in pool
157
173
color_pool = set (range (1 , min_colors + 1 ))
@@ -167,8 +183,10 @@ def balanced(graph, feedback, min_colors = 4):
167
183
reverse = True )]
168
184
# counts for each color already assigned
169
185
color_counts = defaultdict (int )
186
+ color_areas = defaultdict (float )
170
187
for c in color_pool :
171
188
color_counts [c ] = 0
189
+ color_areas [c ] = 0
172
190
173
191
total = 10.0 / len (sorted_by_count )
174
192
i = 0
@@ -183,16 +201,26 @@ def balanced(graph, feedback, min_colors = 4):
183
201
# from the existing colors, work out which are available (ie non-adjacent)
184
202
available_colors = color_pool .difference (adjacent_colors )
185
203
204
+ feature_color = - 1
186
205
if len (available_colors ) == 0 :
187
206
# no existing colors available for this feature, so add new color to pool
188
207
feature_color = len (color_pool ) + 1
189
208
color_pool .add (feature_color )
190
209
else :
191
- # choose least used available color
192
- counts = [(c , v ) for c , v in color_counts .items () if c in available_colors ]
193
- feature_color = sorted (counts , key = operator .itemgetter (1 ))[0 ][0 ]
210
+ if balance == BalanceMethod .BY_COUNT :
211
+ # choose least used available color
212
+ counts = [(c , v ) for c , v in color_counts .items () if c in available_colors ]
213
+ feature_color = sorted (counts , key = operator .itemgetter (1 ))[0 ][0 ]
214
+ color_counts [feature_color ] += 1
215
+ elif balance == BalanceMethod .BY_AREA :
216
+ areas = [(c , v ) for c , v in color_areas .items () if c in available_colors ]
217
+ feature_color = sorted (areas , key = operator .itemgetter (1 ))[0 ][0 ]
218
+ color_areas [feature_color ] += features [feature_id ].geometry ().area ()
219
+ #elif balance==BalanceMethod.BY_DISTANCE:
220
+
221
+
194
222
feature_colors [feature_id ] = feature_color
195
- color_counts [ feature_color ] += 1
223
+
196
224
197
225
i += 1
198
226
feedback .setProgress (70 + int (i * total ))
0 commit comments