Statistics
| Revision:

svn-gvsig-desktop / trunk / extensions / extGeoProcessing / src / com / iver / gvsig / geoprocessing / impl / dissolve / DissolveVisitor.java @ 4993

History | View | Annotate | Download (16.4 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 4987 2006-05-01 19:14:30Z azabala $
47
 * $Log$
48
 * Revision 1.4  2006-05-01 19:14:30  azabala
49
 * comentario
50
 *
51
 * Revision 1.3  2006/03/15 18:33:24  azabala
52
 * *** empty log message ***
53
 *
54
 * Revision 1.2  2006/03/07 21:01:33  azabala
55
 * *** empty log message ***
56
 *
57
 * Revision 1.1  2006/03/06 19:48:39  azabala
58
 * *** empty log message ***
59
 *
60
 * Revision 1.2  2006/03/05 19:58:30  azabala
61
 * *** empty log message ***
62
 *
63
 * Revision 1.1  2006/02/26 20:54:04  azabala
64
 * *** empty log message ***
65
 *
66
 *
67
 */
68
package com.iver.gvsig.geoprocessing.impl.dissolve;
69

    
70
import java.awt.geom.Rectangle2D;
71
import java.util.ArrayList;
72
import java.util.Iterator;
73
import java.util.List;
74
import java.util.Map;
75
import java.util.Stack;
76

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

    
97
/**
98
 * This FeatureVisitor processes each geometry of a polygon layer, looks for
99
 * adjacent polygons and verify values of a dissolve field. If these values are
100
 * coincident, it creates a new polygon by unioning original polygons. Also
101
 * applies a sumarization function to numeric fields of original features.
102
 * 
103
 * @author azabala
104
 * 
105
 * 
106
 * FIXME A?ADIR EL CAMPO A PARTIR DEL QUE SE HACE EL DISSOLVE A LA CAPA
107
 * RESULTADO
108
 * 
109
 */
110
public class DissolveVisitor implements FeatureVisitor {
111

    
112
        /**
113
         * Allows to get attributes of disolved layer features
114
         */
115
        SelectableDataSource recordset;
116

    
117
        /**
118
         * Is used to do spatial querys (looking for adjacent polygons to visited
119
         * feature geometry
120
         */
121
        FLyrVect dissolvedLayer;
122

    
123
        /**
124
         * Field which adjacent features must have the same value to dissolve them
125
         */
126
        String dissolveField;
127

    
128
        /**
129
         * It marks all features that have already been dissolved (to avoid process
130
         * them in subsecuent steps)
131
         * 
132
         */
133
        FBitSet dissolvedGeometries;
134

    
135
        /**
136
         * Relates a numerical field name with its sumarization functions
137
         */
138
        Map numericField_sumarizeFunction;
139

    
140
        /**
141
         * Processes results of dissolve operations (save them in a file, or cache
142
         * them in memory, etc)
143
         */
144
        FeatureProcessor featureProcessor;
145

    
146
        Strategy strategy;
147

    
148
        /**
149
         * Constructor
150
         * 
151
         * @param layerToDissolve
152
         */
153
        public DissolveVisitor(String dissolveField, FeatureProcessor processor) {
154
                this.dissolveField = dissolveField;
155
                this.featureProcessor = processor;
156
                dissolvedGeometries = new FBitSet();
157
        }
158
        
159
        public int getNumProcessedGeometries(){
160
                return dissolvedGeometries.cardinality();
161
        }
162

    
163
        public void setDissolvedAttributesInfo(Map numericField_sumFunction) {
164
                this.numericField_sumarizeFunction = numericField_sumFunction;
165
        }
166

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

    
214
        /**
215
         * Inner class to manage dissolve geoprocess. It mantains feature info
216
         * interesting for dissolve (int index, JTS Geometry, etc)
217
         * 
218
         * @author azabala
219
         * 
220
         */
221
        class DissolvedFeature extends DefaultFeature {
222
                int index;
223

    
224
                Geometry jtsGeometry;
225

    
226
                public DissolvedFeature(IGeometry geom, Value[] att, int index) {
227
                        super(geom, att);
228
                        this.index = index;
229
                }
230

    
231
                public int getIndex() {
232
                        return index;
233
                }
234

    
235
                public Geometry getJtsGeometry() {
236
                        return jtsGeometry;
237
                }
238

    
239
                public void setJtsGeometry(Geometry jtsGeometry) {
240
                        this.jtsGeometry = jtsGeometry;
241
                }
242

    
243
                public IGeometry getGeometry() {
244
                        IGeometry solution = super.getGeometry();
245
                        if (solution == null && jtsGeometry != null) {
246
                                solution = FConverter.jts_to_igeometry(jtsGeometry);
247
                        }
248
                        return solution;
249
                }
250

    
251
        }
252

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

    
285
        /**
286
         * For each individual geometry processed in DissolveVisitor's visit method,
287
         * this Visitor visits its adjacent polygons geometries to check dissolve
288
         * conditions.
289
         * 
290
         * @author azabala
291
         * 
292
         */
293
        class IndividualGeometryDissolveVisitor implements FeatureVisitor {
294
                /**
295
                 * Marks index of features that have been dissolved yet
296
                 */
297
                FBitSet dissolvedFeatures;
298

    
299
                /**
300
                 * It saves all features for we are looking for adjacent geometries.
301
                 * Dissolving is similar to network tracking algorithms: one feature is
302
                 * adjacent to two, two is adjacent to four, etc We will save features
303
                 * to process in this stack
304
                 */
305
                Stack featuresToDissolve;
306

    
307
                /**
308
                 * Field use to dissolve adjacent geometries with the same value for it
309
                 */
310
                String dissolveField;
311

    
312
                /**
313
                 * Maps for each numerical field its sumarization functions
314
                 */
315
                Map fields_sumarizeFunc;
316

    
317
                /**
318
                 * Feature for which we are looking for features to dissolve
319
                 */
320
                DissolvedFeature feature;
321

    
322
                /**
323
                 * jts geometry of feature
324
                 */
325
                private Geometry featureJtsGeo;
326

    
327
                /**
328
                 * Numeric values result of a sumarization operation
329
                 */
330
                private List sumarizedValues;
331

    
332
                /**
333
                 * Recordset to recover attribute values
334
                 */
335
                SelectableDataSource recordset;
336

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

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

    
406
                                                // group by attributes
407
                                                applySumarizeFunction(index);
408

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

    
424
                public void stop(FLayer layer) {
425
                }
426

    
427
                // FIXME Create an abstract FeatureVisitor
428
                public boolean start(FLayer layer) {
429
                        try {
430
                                recordset = ((AlphanumericData) layer).getRecordset();
431
                                this.applySumarizeFunction(feature.getIndex());
432
                                
433
                        } catch (com.iver.cit.gvsig.fmap.DriverException e) {
434
                                return false;
435
                        } catch (DriverException e) {
436
                                return false;
437
                        }
438
                        return true;
439
                }
440

    
441
                public void setFields_sumarizeFunc(Map fields_sumarizeFunc) {
442
                        this.fields_sumarizeFunc = fields_sumarizeFunc;
443
                }
444

    
445
                public Map getFields_sumarizeFunc() {
446
                        return fields_sumarizeFunc;
447
                }
448

    
449
                public Geometry getFeatureJtsGeo() {
450
                        return featureJtsGeo;
451
                }
452

    
453
                public Value[] getSumarizedValues() {
454
                        Value[] solution = new Value[sumarizedValues.size()];
455
                        sumarizedValues.toArray(solution);
456
                        return solution;
457
                }
458

    
459
                public Value[] getSumarizedValues2() {
460
                        Value[] solution = null;
461
                        ArrayList values = new ArrayList();
462
                        Iterator fieldsIt = fields_sumarizeFunc.keySet().iterator();
463
                        while (fieldsIt.hasNext()) {
464
                                String field = (String) fieldsIt.next();
465
                                SummarizationFunction[] functions = (SummarizationFunction[]) fields_sumarizeFunc
466
                                                .get(field);
467
                                for (int i = 0; i < functions.length; i++) {
468
                                        values.add(functions[i].getSumarizeValue());
469
                                }// for
470
                        }// while
471
                        solution = new Value[values.size()];
472
                        values.toArray(solution);
473
                        return solution;
474
                }
475

    
476
                public void setDissolveField(String dissolveField) {
477
                        this.dissolveField = dissolveField;
478

    
479
                }
480

    
481
                public void setProcessedFeature(DissolvedFeature feature2) {
482
                        this.feature = feature2;
483

    
484
                }
485
        }// IndividualDissolve
486

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

    
518
                        strategy.process(visitor, query);
519

    
520
                        Geometry jtsGeo = visitor.getFeatureJtsGeo();
521
                        if (jtsGeometry == null) {
522
                                jtsGeometry = jtsGeo;
523
                        } else {
524
                                jtsGeometry = jtsGeometry.union(jtsGeo);
525
                        }
526
                }// while
527
                Value[] values = visitor.getSumarizedValues2();
528

    
529
                feature = new DissolvedFeature(null, values, -1);
530
                feature.setJtsGeometry(jtsGeometry);
531
                return feature;
532
        }
533

    
534
        public void stop(FLayer layer) {
535
                this.featureProcessor.finish();
536
        }
537

    
538
        public boolean start(FLayer layer) {
539
                if (layer instanceof AlphanumericData && layer instanceof VectorialData) {
540
                        try {
541
                                dissolvedLayer = (FLyrVect) layer;
542
                                recordset = ((AlphanumericData) layer).getRecordset();
543
                                featureProcessor.start();
544
                        } catch (com.iver.cit.gvsig.fmap.DriverException e) {
545
                                return false;
546
                        } catch (EditionException e) {
547
                                return false;
548
                        }
549
                        return true;
550
                }
551
                return false;
552
        }
553

    
554
        public void setStrategy(Strategy strategy) {
555
                this.strategy = strategy;
556
        }
557
        
558
        public String getProcessDescription() {
559
                return "Dissolving polygons of a layer";
560
        }
561

    
562
}