2727
2828import os
2929import operator
30+ from enum import Enum
31+
3032from collections import defaultdict , deque
3133
3234from qgis .core import (QgsField ,
3739from qgis .PyQt .QtCore import (QVariant )
3840
3941from 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 )
4245from processing .core .outputs import OutputVector
4346from processing .tools import dataobjects , vector
4447
4548pluginPath = os .path .split (os .path .split (os .path .dirname (__file__ ))[0 ])[0 ]
4649
50+ class BalanceMethod (Enum ):
51+ BY_COUNT = 0
52+ BY_AREA = 1
53+ BY_DISTANCE = 2
4754
4855class TopoColor (GeoAlgorithm ):
4956 INPUT_LAYER = 'INPUT_LAYER'
5057 MIN_COLORS = 'MIN_COLORS'
58+ BALANCE = 'BALANCE'
5159 OUTPUT_LAYER = 'OUTPUT_LAYER'
5260
61+
5362 def defineCharacteristics (self ):
5463 self .name , self .i18n_name = self .trAlgorithm ('Topological coloring' )
5564 self .group , self .i18n_group = self .trAlgorithm ('Cartographic tools' )
@@ -59,13 +68,21 @@ def defineCharacteristics(self):
5968 self .tr ('Input layer' ), [dataobjects .TYPE_VECTOR_POLYGON ]))
6069 self .addParameter (ParameterNumber (self .MIN_COLORS ,
6170 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 ))
6278
6379 self .addOutput (OutputVector (self .OUTPUT_LAYER , self .tr ('Colored' ), datatype = [dataobjects .TYPE_VECTOR_POLYGON ]))
6480
6581 def processAlgorithm (self , feedback ):
6682 layer = dataobjects .getObjectFromUri (
6783 self .getParameterValue (self .INPUT_LAYER ))
6884 min_colors = self .getParameterValue (self .MIN_COLORS )
85+ balance_by = BalanceMethod (self .getParameterValue (self .BALANCE ))
6986
7087 fields = layer .fields ()
7188 fields .append (QgsField ('color_id' , QVariant .Int ))
@@ -76,26 +93,25 @@ def processAlgorithm(self, feedback):
7693 layer .wkbType (),
7794 layer .crs ())
7895
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 )}
8297
8398 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 ,
86103 min_colors = min_colors )
87104
88105 max_colors = max (feature_colors .values ())
89106 feedback .pushInfo (self .tr ('{} colors required' ).format (max_colors ))
90107
91108 total = 20.0 / len (features )
92109 current = 0
93- while features :
94- input_feature = features .popleft ()
110+ for feature_id , input_feature in features .items ():
95111 output_feature = input_feature
96112 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 ])
99115 else :
100116 attributes .append (NULL )
101117 output_feature .setAttributes (attributes )
@@ -115,7 +131,7 @@ def compute_graph(features, feedback, create_id_graph=False):
115131 id_graph = Graph (sort_graph = True )
116132
117133 # 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 () }
119135
120136 total = 70.0 / len (features_with_geometry )
121137 index = QgsSpatialIndex ()
@@ -151,7 +167,7 @@ def compute_graph(features, feedback, create_id_graph=False):
151167class ColoringAlgorithm :
152168
153169 @staticmethod
154- def balanced (graph , feedback , min_colors = 4 ):
170+ def balanced (features , graph , feedback , balance = BalanceMethod . BY_COUNT , min_colors = 4 ):
155171 feature_colors = {}
156172 # start with minimum number of colors in pool
157173 color_pool = set (range (1 , min_colors + 1 ))
@@ -167,8 +183,10 @@ def balanced(graph, feedback, min_colors = 4):
167183 reverse = True )]
168184 # counts for each color already assigned
169185 color_counts = defaultdict (int )
186+ color_areas = defaultdict (float )
170187 for c in color_pool :
171188 color_counts [c ] = 0
189+ color_areas [c ] = 0
172190
173191 total = 10.0 / len (sorted_by_count )
174192 i = 0
@@ -183,16 +201,26 @@ def balanced(graph, feedback, min_colors = 4):
183201 # from the existing colors, work out which are available (ie non-adjacent)
184202 available_colors = color_pool .difference (adjacent_colors )
185203
204+ feature_color = - 1
186205 if len (available_colors ) == 0 :
187206 # no existing colors available for this feature, so add new color to pool
188207 feature_color = len (color_pool ) + 1
189208 color_pool .add (feature_color )
190209 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+
194222 feature_colors [feature_id ] = feature_color
195- color_counts [ feature_color ] += 1
223+
196224
197225 i += 1
198226 feedback .setProgress (70 + int (i * total ))
0 commit comments