Statistics
| Revision:

root / trunk / extensions / extGeoProcessing / src / com / iver / gvsig / geoprocessing / impl / dissolve / DissolveVisitor.java @ 4447

History | View | Annotate | Download (16.2 KB)

1
/*
2
 * Created on 24-feb-2006
3
 *
4
 * gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
5
 *
6
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * as published by the Free Software Foundation; either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
21
 *
22
 * For more information, contact:
23
 *
24
 *  Generalitat Valenciana
25
 *   Conselleria d'Infraestructures i Transport
26
 *   Av. Blasco Ib??ez, 50
27
 *   46010 VALENCIA
28
 *   SPAIN
29
 *
30
 *      +34 963862235
31
 *   gvsig@gva.es
32
 *      www.gvsig.gva.es
33
 *
34
 *    or
35
 *
36
 *   IVER T.I. S.A
37
 *   Salamanca 50
38
 *   46005 Valencia
39
 *   Spain
40
 *
41
 *   +34 963163400
42
 *   dac@iver.es
43
 */
44
/* CVS MESSAGES:
45
 *
46
 * $Id: DissolveVisitor.java 4447 2006-03-15 18:35:56Z azabala $
47
 * $Log$
48
 * Revision 1.3  2006-03-15 18:33:24  azabala
49
 * *** empty log message ***
50
 *
51
 * Revision 1.2  2006/03/07 21:01:33  azabala
52
 * *** empty log message ***
53
 *
54
 * Revision 1.1  2006/03/06 19:48:39  azabala
55
 * *** empty log message ***
56
 *
57
 * Revision 1.2  2006/03/05 19:58:30  azabala
58
 * *** empty log message ***
59
 *
60
 * Revision 1.1  2006/02/26 20:54:04  azabala
61
 * *** empty log message ***
62
 *
63
 *
64
 */
65
package com.iver.gvsig.geoprocessing.impl.dissolve;
66

    
67
import java.awt.geom.Rectangle2D;
68
import java.util.ArrayList;
69
import java.util.Iterator;
70
import java.util.List;
71
import java.util.Map;
72
import java.util.Stack;
73

    
74
import com.hardcode.gdbms.engine.data.driver.DriverException;
75
import com.hardcode.gdbms.engine.values.NumericValue;
76
import com.hardcode.gdbms.engine.values.Value;
77
import com.iver.cit.gvsig.fmap.core.DefaultFeature;
78
import com.iver.cit.gvsig.fmap.core.IGeometry;
79
import com.iver.cit.gvsig.fmap.core.v02.FConverter;
80
import com.iver.cit.gvsig.fmap.edition.EditionException;
81
import com.iver.cit.gvsig.fmap.layers.FBitSet;
82
import com.iver.cit.gvsig.fmap.layers.FLayer;
83
import com.iver.cit.gvsig.fmap.layers.FLyrVect;
84
import com.iver.cit.gvsig.fmap.layers.SelectableDataSource;
85
import com.iver.cit.gvsig.fmap.layers.layerOperations.AlphanumericData;
86
import com.iver.cit.gvsig.fmap.layers.layerOperations.VectorialData;
87
import com.iver.cit.gvsig.fmap.operations.strategies.FeatureVisitor;
88
import com.iver.cit.gvsig.fmap.operations.strategies.Strategy;
89
import com.iver.cit.gvsig.fmap.operations.strategies.VisitException;
90
import com.iver.gvsig.geoprocessing.model.FeatureProcessor;
91
import com.iver.gvsig.geoprocessing.model.SummarizationFunction;
92
import com.vividsolutions.jts.geom.Geometry;
93

    
94
/**
95
 * This FeatureVisitor processes each geometry of a polygon layer, looks for
96
 * adjacent polygons and verify values of a dissolve field. If these values are
97
 * coincident, it creates a new polygon by unioning original polygons. Also
98
 * applies a sumarization function to numeric fields of original features.
99
 * 
100
 * @author azabala
101
 * 
102
 */
103
public class DissolveVisitor implements FeatureVisitor {
104

    
105
        /**
106
         * Allows to get attributes of disolved layer features
107
         */
108
        SelectableDataSource recordset;
109

    
110
        /**
111
         * Is used to do spatial querys (looking for adjacent polygons to visited
112
         * feature geometry
113
         */
114
        FLyrVect dissolvedLayer;
115

    
116
        /**
117
         * Field which adjacent features must have the same value to dissolve them
118
         */
119
        String dissolveField;
120

    
121
        /**
122
         * It marks all features that have already been dissolved (to avoid process
123
         * them in subsecuent steps)
124
         * 
125
         */
126
        FBitSet dissolvedGeometries;
127

    
128
        /**
129
         * Relates a numerical field name with its sumarization functions
130
         */
131
        Map numericField_sumarizeFunction;
132

    
133
        /**
134
         * Processes results of dissolve operations (save them in a file, or cache
135
         * them in memory, etc)
136
         */
137
        FeatureProcessor featureProcessor;
138

    
139
        Strategy strategy;
140

    
141
        /**
142
         * Constructor
143
         * 
144
         * @param layerToDissolve
145
         */
146
        public DissolveVisitor(String dissolveField, FeatureProcessor processor) {
147
                this.dissolveField = dissolveField;
148
                this.featureProcessor = processor;
149
                dissolvedGeometries = new FBitSet();
150
        }
151
        
152
        public int getNumProcessedGeometries(){
153
                return dissolvedGeometries.cardinality();
154
        }
155

    
156
        public void setDissolvedAttributesInfo(Map numericField_sumFunction) {
157
                this.numericField_sumarizeFunction = numericField_sumFunction;
158
        }
159

    
160
        /*
161
         * Algorithm to compute dissolve is strongly based in depth first algorithm
162
         * to traverse graphs.
163
         * 
164
         * It puts features to dissolve in a stack. While stack is not empty, get
165
         * Features and looks for adjacent to it. For each adjacent feature, verify
166
         * its dissolve field value, and if it is similar to feature to dissolve
167
         * with, obtain a new feature by unioning their geometries and by applying
168
         * sumarization functions to its numeric attributes. For each adjacent
169
         * feature, put it in the Stack
170
         */
171
        public void visit(IGeometry g, int index) throws VisitException {
172
                if (!dissolvedGeometries.get(index)) {
173
                        // if we havent dissolved this feature
174
                        Stack toDissol = new Stack();// stack for adjacent features
175
                        DissolvedFeature feature;
176
                        try {
177
                                feature = createFeature(g, index);
178
                                toDissol.push(feature);
179
                                DissolvedFeature dissolved = dissolve(toDissol);
180
                                this.featureProcessor.processFeature(dissolved);
181
                                resetFunctions();
182
                        } catch (DriverException e) {
183
                                throw new VisitException(
184
                                                "Error al procesar las geometrias a fusionar durante dissolve");
185
                        } catch (com.iver.cit.gvsig.fmap.DriverException e) {
186
                                throw new VisitException(
187
                                                "Error al procesar las geometrias a fusionar durante dissolve");
188
                        }
189
                }// if
190
        }
191
        /**
192
         * FIXME Redise?ar esto, pues el codigo es similar al de Spatial Join
193
         *
194
         */
195
        void resetFunctions() {
196
                Iterator fieldsIt = numericField_sumarizeFunction.keySet().iterator();
197
                while (fieldsIt.hasNext()) {
198
                        String field = (String) fieldsIt.next();
199
                        SummarizationFunction[] functions = (SummarizationFunction[]) numericField_sumarizeFunction
200
                                        .get(field);
201
                        for (int i = 0; i < functions.length; i++) {
202
                                functions[i].reset();
203
                        }// for
204
                }// while
205
        }
206

    
207
        /**
208
         * Inner class to manage dissolve geoprocess. It mantains feature info
209
         * interesting for dissolve (int index, JTS Geometry, etc)
210
         * 
211
         * @author azabala
212
         * 
213
         */
214
        class DissolvedFeature extends DefaultFeature {
215
                int index;
216

    
217
                Geometry jtsGeometry;
218

    
219
                public DissolvedFeature(IGeometry geom, Value[] att, int index) {
220
                        super(geom, att);
221
                        this.index = index;
222
                }
223

    
224
                public int getIndex() {
225
                        return index;
226
                }
227

    
228
                public Geometry getJtsGeometry() {
229
                        return jtsGeometry;
230
                }
231

    
232
                public void setJtsGeometry(Geometry jtsGeometry) {
233
                        this.jtsGeometry = jtsGeometry;
234
                }
235

    
236
                public IGeometry getGeometry() {
237
                        IGeometry solution = super.getGeometry();
238
                        if (solution == null && jtsGeometry != null) {
239
                                solution = FConverter.jts_to_igeometry(jtsGeometry);
240
                        }
241
                        return solution;
242
                }
243

    
244
        }
245

    
246
        /**
247
         * Creates a new IFeature with util info for dissolve geoprocess (it ignores
248
         * non numerical values, etc)
249
         * 
250
         * @param g
251
         * @param index
252
         * @return
253
         * @throws DriverException
254
         */
255
        private DissolvedFeature createFeature(IGeometry g, int index)
256
                        throws DriverException {
257
                DissolvedFeature solution = null;
258
                int numNumericFields = numericField_sumarizeFunction.keySet().size();
259
                // attributes will be dissolve field and sumarized function for
260
                // numerical
261
                // values
262
                Value[] values = new Value[numNumericFields + 1];
263
                Iterator fieldIt = numericField_sumarizeFunction.keySet().iterator();
264
                int valueIndex = 0;
265
                while (fieldIt.hasNext()) {
266
                        String fieldName = (String) fieldIt.next();
267
                        int fieldIndex = recordset.getFieldIndexByName(fieldName);
268
                        values[valueIndex] = recordset.getFieldValue(index, fieldIndex);
269
                        valueIndex++;
270
                }
271
                int dissolveField = recordset.getFieldIndexByName(this.dissolveField);
272
                values[numNumericFields] = recordset
273
                                .getFieldValue(index, dissolveField);
274
                solution = new DissolvedFeature(g, values, index);
275
                return solution;
276
        }
277

    
278
        /**
279
         * For each individual geometry processed in DissolveVisitor's visit method,
280
         * this Visitor visits its adjacent polygons geometries to check dissolve
281
         * conditions.
282
         * 
283
         * @author azabala
284
         * 
285
         */
286
        class IndividualGeometryDissolveVisitor implements FeatureVisitor {
287
                /**
288
                 * Marks index of features that have been dissolved yet
289
                 */
290
                FBitSet dissolvedFeatures;
291

    
292
                /**
293
                 * It saves all features for we are looking for adjacent geometries.
294
                 * Dissolving is similar to network tracking algorithms: one feature is
295
                 * adjacent to two, two is adjacent to four, etc We will save features
296
                 * to process in this stack
297
                 */
298
                Stack featuresToDissolve;
299

    
300
                /**
301
                 * Field use to dissolve adjacent geometries with the same value for it
302
                 */
303
                String dissolveField;
304

    
305
                /**
306
                 * Maps for each numerical field its sumarization functions
307
                 */
308
                Map fields_sumarizeFunc;
309

    
310
                /**
311
                 * Feature for which we are looking for features to dissolve
312
                 */
313
                DissolvedFeature feature;
314

    
315
                /**
316
                 * jts geometry of feature
317
                 */
318
                private Geometry featureJtsGeo;
319

    
320
                /**
321
                 * Numeric values result of a sumarization operation
322
                 */
323
                private List sumarizedValues;
324

    
325
                /**
326
                 * Recordset to recover attribute values
327
                 */
328
                SelectableDataSource recordset;
329

    
330
                IndividualGeometryDissolveVisitor(DissolvedFeature feature,
331
                                FBitSet dissolvedFeatures, Stack featuresToDissolve, Map fields_sumarize) {
332
                        this.dissolvedFeatures = dissolvedFeatures;
333
                        this.feature = feature;
334
                        this.featuresToDissolve = featuresToDissolve;
335
                        this.featureJtsGeo = feature.getGeometry().toJTSGeometry();
336
                        this.sumarizedValues = new ArrayList();
337
                        this.fields_sumarizeFunc = fields_sumarize;
338
                        
339
                }
340
                
341
                public String getProcessDescription() {
342
                        return "Dissolving a polygon with its adjacents";
343
                }
344
                
345
                /**
346
                 * Applies to sumarization functions feature values.
347
                 * @throws DriverException 
348
                 * 
349
                 * FIXME Redise?ar, pues el codigo es similar al de Spatial Join
350
                 */
351
                private void applySumarizeFunction(int recordIndex) 
352
                                                                                throws DriverException{
353
                        Iterator fieldsIt = fields_sumarizeFunc.keySet()
354
                                        .iterator();
355
                        while (fieldsIt.hasNext()) {
356
                                String field = (String) fieldsIt.next();
357
                                int fieldIndex = recordset.getFieldIndexByName(field);
358
                                Value valToSumarize = recordset.getFieldValue(
359
                                                recordIndex, fieldIndex);
360
                                SummarizationFunction[] functions = (SummarizationFunction[]) fields_sumarizeFunc
361
                                                .get(field);
362
                                for (int i = 0; i < functions.length; i++) {
363
                                        functions[i]
364
                                                        .process((NumericValue) valToSumarize);
365
                                        sumarizedValues.add(functions[i]
366
                                                        .getSumarizeValue());
367
                                }// for
368
                        }// while
369
                }
370

    
371
                public void visit(IGeometry g, int index) throws VisitException {
372
                        // Its the feature whose adjacents we are looking for?
373
                        if (index == feature.getIndex())
374
                                return;
375
                        // have we dissolved this feature yet?
376
                        if (dissolvedFeatures.get(index))
377
                                return;
378
                        try {
379
                                DissolvedFeature adjacentFeature = createFeature(g, index);
380
                                IGeometry geometry = adjacentFeature.getGeometry();
381
                                Geometry jtsGeo = geometry.toJTSGeometry();
382
                                adjacentFeature.setJtsGeometry(jtsGeo);
383
                                if (jtsGeo.touches(featureJtsGeo)) {// They are adjacent
384
                                        // dissolveField is the last
385
                                        int fieldIndex = numericField_sumarizeFunction.keySet()
386
                                                        .size();
387
                                        Value adjacentVal = adjacentFeature
388
                                                        .getAttribute(fieldIndex);
389
                                        Value val = feature.getAttribute(fieldIndex);
390
                                        if (adjacentVal.doEquals(val)) {
391
                                                dissolvedFeatures.set(index);
392
                                                // we actualize geometry by unioning
393
                                                featuresToDissolve.push(adjacentFeature);
394
                                                DissolvedFeature fet = dissolve(featuresToDissolve);
395
                                                // group by geometry
396
                                                featureJtsGeo = featureJtsGeo.union(fet
397
                                                                .getJtsGeometry());
398

    
399
                                                // group by attributes
400
                                                applySumarizeFunction(index);
401

    
402
                                        }// if val equals
403
                                }// if touches
404
                                /*
405
                                 * else{ //must we cache IGeometries that dont have the same
406
                                 * values //for speed up next processes? }
407
                                 */
408
                        } catch (DriverException e) {
409
                                throw new VisitException(
410
                                                "Error al cargar los pol?gonos adyacentes durante un dissolve");
411
                        } catch (com.iver.cit.gvsig.fmap.DriverException e) {
412
                                throw new VisitException(
413
                                                "Error al procesar los pol?gonos adyacentes durante un dissolve");
414
                        }
415
                }// visit
416

    
417
                public void stop(FLayer layer) {
418
                }
419

    
420
                // FIXME Create an abstract FeatureVisitor
421
                public boolean start(FLayer layer) {
422
                        try {
423
                                recordset = ((AlphanumericData) layer).getRecordset();
424
                                this.applySumarizeFunction(feature.getIndex());
425
                                
426
                        } catch (com.iver.cit.gvsig.fmap.DriverException e) {
427
                                return false;
428
                        } catch (DriverException e) {
429
                                return false;
430
                        }
431
                        return true;
432
                }
433

    
434
                public void setFields_sumarizeFunc(Map fields_sumarizeFunc) {
435
                        this.fields_sumarizeFunc = fields_sumarizeFunc;
436
                }
437

    
438
                public Map getFields_sumarizeFunc() {
439
                        return fields_sumarizeFunc;
440
                }
441

    
442
                public Geometry getFeatureJtsGeo() {
443
                        return featureJtsGeo;
444
                }
445

    
446
                public Value[] getSumarizedValues() {
447
                        Value[] solution = new Value[sumarizedValues.size()];
448
                        sumarizedValues.toArray(solution);
449
                        return solution;
450
                }
451

    
452
                public Value[] getSumarizedValues2() {
453
                        Value[] solution = null;
454
                        ArrayList values = new ArrayList();
455
                        Iterator fieldsIt = fields_sumarizeFunc.keySet().iterator();
456
                        while (fieldsIt.hasNext()) {
457
                                String field = (String) fieldsIt.next();
458
                                SummarizationFunction[] functions = (SummarizationFunction[]) fields_sumarizeFunc
459
                                                .get(field);
460
                                for (int i = 0; i < functions.length; i++) {
461
                                        values.add(functions[i].getSumarizeValue());
462
                                }// for
463
                        }// while
464
                        solution = new Value[values.size()];
465
                        values.toArray(solution);
466
                        return solution;
467
                }
468

    
469
                public void setDissolveField(String dissolveField) {
470
                        this.dissolveField = dissolveField;
471

    
472
                }
473

    
474
                public void setProcessedFeature(DissolvedFeature feature2) {
475
                        this.feature = feature2;
476

    
477
                }
478
        }// IndividualDissolve
479

    
480
        private DissolvedFeature dissolve(Stack toDissol)
481
                        throws com.iver.cit.gvsig.fmap.DriverException, VisitException {
482
                DissolvedFeature feature = null;
483
                Geometry jtsGeometry = null;
484
                IndividualGeometryDissolveVisitor visitor = null;
485
                while (toDissol.size() != 0) {
486
                        feature = (DissolvedFeature) toDissol.pop();
487
                        // flags this idx (to not to process in future)
488
                        dissolvedGeometries.set(feature.getIndex());
489
                        if (visitor == null) {
490
                                visitor = new IndividualGeometryDissolveVisitor(feature,
491
                                                dissolvedGeometries, toDissol, numericField_sumarizeFunction);
492
                                visitor.setDissolveField(this.dissolveField);
493
                        } else {
494
                                visitor.setProcessedFeature(feature);
495
                        }
496
                        // FIXME Si hacemos una busqueda de adyacencia a partir de un
497
                        // rectangulo
498
                        // nunca obtendremos resultados, porque no intersectan.
499
                        // De momento agrandamos el rectangulo, pero lo que hay que hacer es
500
                        // implementar
501
                        // process(FeatureVisitor, IGeometry) (SelectByShapeVisitor)
502
                        Rectangle2D bounds = feature.getGeometry().getBounds2D();
503
                        double xmin = bounds.getMinX();
504
                        double ymin = bounds.getMinY();
505
                        double xmax = bounds.getMaxX();
506
                        double ymax = bounds.getMaxY();
507
                        double magnify = 15d;
508
                        Rectangle2D query = new Rectangle2D.Double(xmin - magnify, ymin
509
                                        - magnify, (xmax - xmin) + magnify, (ymax - ymin) + magnify);
510

    
511
                        strategy.process(visitor, query);
512

    
513
                        Geometry jtsGeo = visitor.getFeatureJtsGeo();
514
                        if (jtsGeometry == null) {
515
                                jtsGeometry = jtsGeo;
516
                        } else {
517
                                jtsGeometry = jtsGeometry.union(jtsGeo);
518
                        }
519
                }// while
520
                Value[] values = visitor.getSumarizedValues2();
521

    
522
                feature = new DissolvedFeature(null, values, -1);
523
                feature.setJtsGeometry(jtsGeometry);
524
                return feature;
525
        }
526

    
527
        public void stop(FLayer layer) {
528
                this.featureProcessor.finish();
529
        }
530

    
531
        public boolean start(FLayer layer) {
532
                if (layer instanceof AlphanumericData && layer instanceof VectorialData) {
533
                        try {
534
                                dissolvedLayer = (FLyrVect) layer;
535
                                recordset = ((AlphanumericData) layer).getRecordset();
536
                                featureProcessor.start();
537
                        } catch (com.iver.cit.gvsig.fmap.DriverException e) {
538
                                return false;
539
                        } catch (EditionException e) {
540
                                return false;
541
                        }
542
                        return true;
543
                }
544
                return false;
545
        }
546

    
547
        public void setStrategy(Strategy strategy) {
548
                this.strategy = strategy;
549
        }
550
        
551
        public String getProcessDescription() {
552
                return "Dissolving polygons of a layer";
553
        }
554

    
555
}