Statistics
| Revision:

svn-gvsig-desktop / trunk / libraries / libTopology / src / org / gvsig / jts / GeometrySnapper.java @ 19632

History | View | Annotate | Download (16.3 KB)

1
/*
2
 * Created on 07-sep-2007
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: 
47
 * $Log: 
48
 *
49
 */
50
package org.gvsig.jts;
51

    
52
import java.util.ArrayList;
53
import java.util.List;
54

    
55
import com.iver.cit.gvsig.util.SnappingCoordinateMap;
56
import com.vividsolutions.jts.algorithms.SnapCGAlgorithms;
57
import com.vividsolutions.jts.geom.Coordinate;
58
import com.vividsolutions.jts.geom.CoordinateList;
59
import com.vividsolutions.jts.geom.Geometry;
60
import com.vividsolutions.jts.geom.GeometryCollection;
61
import com.vividsolutions.jts.geom.LineString;
62
import com.vividsolutions.jts.geom.LinearRing;
63
import com.vividsolutions.jts.geom.MultiLineString;
64
import com.vividsolutions.jts.geom.MultiPolygon;
65
import com.vividsolutions.jts.geom.Polygon;
66

    
67
/**
68
 * Moves coordinates of geometries (in a given tolerance) 
69
 * to force them to coincide with coordinates of other geometries 
70
 * in its proximity (the snap tolerance).
71
 * 
72
 * Also, it could normalize coordinates of the same geometry. By normalize
73
 * we mean to force the coherence of isClosed method and coordinates.
74
 * (for example, LINESTRING(100 100, 200 100, 200 200, 100 200, 100 100.01)
75
 *  return 'true' to isClosed call. After normalize the last coordinate
76
 *  will be (100 100))
77
 * 
78
 */
79
public class GeometrySnapper {
80

    
81
        private double snapTolerance;
82
        
83
        public GeometrySnapper(double snapTolerance) {
84
                this.snapTolerance = snapTolerance;
85
        }
86
        
87
        /**
88
         * Utility class for compute coordinate average
89
         * */
90
        class SnapCoordinate {
91
                int weight;
92
                Coordinate coordinate;
93
        }
94

    
95
        /**
96
         * Snaps the coordinates of many geometries with the same weight
97
         * (all coordinates must move)
98
         * @param geoms
99
         * @return
100
         */
101
        public Geometry[] snap(Geometry[] geoms){
102
                int weight[] = new int[geoms.length];
103
                for(int i = 0; i < geoms.length; i++){
104
                        weight[i] = 1;
105
                }
106
                return snap(geoms, weight);
107
        }
108
        
109
        /**
110
         * Snaps all the coordinates of the given geometries
111
         * .
112
         * If many coords are in the same distance tolerance radius, it applies a weighted
113
         * average. If one geometry's weight is 0, it wont participate in the average.
114
         * 
115
         * @param geoms geometries whose coordinates we want to snap
116
         * @param weights weight of each geometry in the computation of the weighted average
117
         * 
118
         * */
119
        public Geometry[] snap(Geometry[] geoms, int[] weights) {
120
                //FIXME En caso de que ve
121
                for(int i = 0; i < geoms.length; i++){
122
                        if(geoms[i] instanceof GeometryCollection){
123
                                GeometryCollection geometryCollection = (GeometryCollection)geoms[i];
124
                                if(geometryCollection.getNumGeometries() > 1)
125
                                        throw new IllegalArgumentException("El metodo snap no puede recibir como "+
126
                                                                "argumentos GeometryCollection o subclases con mas de un elemento");
127
                        }//if
128
                }
129
                
130
                SnappingCoordinateMap coordinateMap = new SnappingCoordinateMap(snapTolerance);
131
                Geometry[] solution = new Geometry[geoms.length];
132
                for(int i = 0; i < geoms.length; i++){//for each geometry
133
                        Geometry geom = geoms[i];                        
134
                        Coordinate[] coords = geom.getCoordinates();
135
                        CoordinateList coordList = new CoordinateList(coords);
136
                        
137
                        for(int j = 0; j < coords.length; j++){
138
                                Coordinate coord = coords[j];
139
                                Coordinate existingSolution = (Coordinate) coordinateMap.get(coord);
140
                                if(coordinateMap.get(coord) != null){//this coordinate has already been snapped
141
                                        coordList.set(j, existingSolution);
142
                                        continue;
143
                                }
144
                                
145
                                ArrayList<SnapCoordinate> coordsToSnap = new ArrayList<SnapCoordinate>();
146
                                SnapCoordinate snapCoord = new SnapCoordinate();
147
                                snapCoord.weight = weights[i];
148
                                snapCoord.coordinate = coord;
149
                                coordsToSnap.add(snapCoord);
150
                                
151
                                for (int k = 0; k < geoms.length; k++){
152
                                        Geometry geom2 = geoms[k];
153
                                        if(i == k)
154
                                                continue;
155
                                        Coordinate[] coords2 = geom2.getCoordinates();
156
                                        for( int z = 0; z < coords2.length; z++){
157
                                                Coordinate coord2 = coords2[z];
158
                                                if(SnapCGAlgorithms.snapEquals2D(coord, coord2, snapTolerance)){
159
                                                        snapCoord = new SnapCoordinate();
160
                                                        snapCoord.weight = weights[k];
161
                                                        snapCoord.coordinate = coord2;
162
                                                        coordsToSnap.add(snapCoord);
163
                                                }//if
164
                                        }//for z
165
                                }//for k
166
                                
167
                                Coordinate newCoord = snapAverage(coordsToSnap);
168
                                coordList.set(j, newCoord);
169
                                coordinateMap.put(newCoord, newCoord);
170
                        }//for j
171
                        
172
                        Coordinate[] newPts = coordList.toCoordinateArray();
173
                        
174
                        String geometryType = geom.getGeometryType();
175
                        if(geometryType.equalsIgnoreCase("GeometryCollection") ||
176
                           geometryType.equalsIgnoreCase("MultiPolygon") ||
177
                           geometryType.equalsIgnoreCase("MultiLineString") ||
178
                           geometryType.equalsIgnoreCase("MultiPoint")){
179
                                
180
                                GeometryCollection geomCol = (GeometryCollection) geom;
181
                                
182
                                //we have already checked that it has no more than 1 geometry
183
                                geom = geomCol.getGeometryN(0);
184
                                geometryType = geom.getGeometryType();
185
                        }
186
                        
187
                        
188
                        
189
                        if(geometryType.equalsIgnoreCase("Polygon")){
190
                                int[] indexOfParts = JtsUtil.getIndexOfParts((Polygon)geom);
191
                                Polygon polygon = JtsUtil.createPolygon(newPts, indexOfParts);
192
                                solution[i] = polygon;
193
                        }else{
194
                                solution[i] = JtsUtil.createGeometry(newPts, geometryType);
195
                        }
196
                }//for i
197
                return solution;
198
        }
199
        
200
        
201
        
202
        public Geometry snapWith(Geometry geom2snap, Geometry[] geoms){
203
                int[] weights = new int[geoms.length];
204
                for(int i = 0; i < weights.length; i++){
205
                        weights[i] = 1;
206
                }
207
                return snapWith(geom2snap, geoms, 1, weights);
208
        }
209
        
210
        
211
        /**
212
         * Similar to the previous method, but only returns the first geometry snapped with the
213
         * rest of geometries
214
         * @param geom2snap
215
         * @param snappingGeometries
216
         * @return
217
         */
218
        public Geometry snapWith(Geometry geom2snap, Geometry[] geoms, int weightGeom, int[] weights){
219
                if(geom2snap instanceof GeometryCollection){
220
                        GeometryCollection geometryCollection = (GeometryCollection)geom2snap;
221
                        if(geometryCollection.getNumGeometries() > 1)
222
                                throw new IllegalArgumentException("El metodo snap no puede recibir como "+
223
                                                        "argumentos GeometryCollection o subclases con mas de un elemento");
224
                
225
                }
226
                
227
                SnappingCoordinateMap coordinateMap = new SnappingCoordinateMap(snapTolerance);
228
                Geometry solution = null;
229
                        
230
                Coordinate[] coords = geom2snap.getCoordinates();
231
                CoordinateList coordList = new CoordinateList(coords);
232
                        
233
                for(int j = 0; j < coords.length; j++){ //for each coordinate of the geometry to crack
234
                        Coordinate coord = coords[j];
235
                        Coordinate existingSolution = (Coordinate) coordinateMap.get(coord);
236
                
237
                        if(coordinateMap.get(coord) != null){//this coordinate has already been snapped
238
                                coordList.set(j, existingSolution);//we exchange the coordinate for the already computed
239
                                continue;
240
                        }
241
                        
242
                        //for the crack operation, we are going to compute the weighted average of a coordinate
243
                        //with all the coordinates of the cracking geometries in cluster tolerance radius
244
                        ArrayList<SnapCoordinate> coordsToSnap = new ArrayList<SnapCoordinate>();
245
                        
246
                        //we add the current coordinate
247
                        SnapCoordinate snapCoord = new SnapCoordinate();
248
                        snapCoord.weight = weightGeom;
249
                        snapCoord.coordinate = coord;
250
                        coordsToSnap.add(snapCoord);
251
                        
252
                        //and now we look for coordinates in the snap tolerance radius
253
                        
254
                        for (int k = 0; k < geoms.length; k++){//for each cracking geometry
255
                                Geometry geom2 = geoms[k];
256
                                Coordinate[] coords2 = geom2.getCoordinates();
257
                                for( int z = 0; z < coords2.length; z++){
258
                                        Coordinate coord2 = coords2[z];
259
                                        if(SnapCGAlgorithms.snapEquals2D(coord, coord2, snapTolerance)){
260
                                                snapCoord = new SnapCoordinate();
261
                                                snapCoord.weight = weights[k];
262
                                                snapCoord.coordinate = coord2;
263
                                                coordsToSnap.add(snapCoord);
264
                                        }//if
265
                                }//for z
266
                        }//for k
267
                        
268
                        Coordinate newCoord = snapAverage(coordsToSnap);
269
                        coordList.set(j, newCoord);
270
                        coordinateMap.put(newCoord, newCoord);
271
                }//for j
272
                        
273
                Coordinate[] newPts = coordList.toCoordinateArray();
274
                
275
                String geometryType = geom2snap.getGeometryType();
276
                
277
                if(geometryType.equalsIgnoreCase("GeometryCollection") ||
278
                   geometryType.equalsIgnoreCase("MultiPolygon") ||
279
                   geometryType.equalsIgnoreCase("MultiLineString") ||
280
                   geometryType.equalsIgnoreCase("MultiPoint")){
281
                        
282
                        GeometryCollection geomCol = (GeometryCollection) geom2snap;
283
                        
284
                        //we have already checked that it has no more than 1 geometry
285
                        geom2snap = geomCol.getGeometryN(0);
286
                        geometryType = geom2snap.getGeometryType();
287
                }
288
                
289
                
290
                if(geometryType.equalsIgnoreCase("Polygon")){
291
                        int[] indexOfParts = JtsUtil.getIndexOfParts((Polygon)geom2snap);
292
                        solution = JtsUtil.createPolygon(newPts, indexOfParts);
293
                }else{
294
                        solution= JtsUtil.createGeometry(newPts, geometryType);
295
                }
296
                
297
                return solution;
298
        }
299
        
300
        /**
301
         * Snaps the coordinates of the given geometry to avoid coordinates
302
         * at a distance lower than the cluster tolerance.
303
         * 
304
         * @param geom original geometry
305
         * @return geometry whose vertex has been snapped.
306
         *    Maybe this solution wont pass the isValid() JTS test
307
         *    (we dont check for 
308
         * 
309
         * 
310
         * 
311
         * 
312
         * @throws IllegalArgumentException if the resulting geometry
313
         * doesnt pass the main checks of geometry construction
314
         * 
315
         *  
316
         */
317
        public Geometry snap(Geometry geom) throws GeometryCollapsedException{
318
                Geometry solution = null;
319
                Coordinate[] coords = null;
320
                if(geom.getGeometryType().equalsIgnoreCase("MultiLineString")){
321
                        MultiLineString multiLine = (MultiLineString) geom;
322
                        int numGeometries = multiLine.getNumGeometries();
323
                        LineString[] lineStrings = new LineString[numGeometries];
324
                        for(int i = 0; i < numGeometries; i++){
325
                                lineStrings[i] = (LineString) snap(multiLine.getGeometryN(i));
326
                        }
327
                        return JtsUtil.GEOMETRY_FACTORY.createMultiLineString(lineStrings);
328
                        
329
                }else if(geom.getGeometryType().equalsIgnoreCase("MultiPolygon")){
330
                        MultiPolygon multiPolygon = (MultiPolygon) geom;
331
                        int numGeometries = multiPolygon.getNumGeometries();
332
                        Polygon[] polygons = new Polygon[numGeometries];
333
                        for(int i = 0; i < numGeometries; i++){
334
                                polygons[i] = (Polygon) snap(multiPolygon.getGeometryN(i));
335
                        }
336
                        return JtsUtil.GEOMETRY_FACTORY.createMultiPolygon(polygons);
337
                        
338
                        
339
                }else if(geom.getGeometryType().equalsIgnoreCase("GeometryCollection")){
340
                        GeometryCollection geomCol = (GeometryCollection) geom;
341
                        int numGeometries = geomCol.getNumGeometries();
342
                        Geometry[] geoms = new Geometry[numGeometries];
343
                        for(int i = 0; i < numGeometries; i++){
344
                                geoms[i] = snap(geomCol.getGeometryN(i));
345
                        }
346
                        return JtsUtil.GEOMETRY_FACTORY.createGeometryCollection(geoms);
347
                }else if(geom.getGeometryType().equalsIgnoreCase("LineString") || 
348
                                        geom.getGeometryType().equalsIgnoreCase("LinearRing")){
349
                        coords = ((LineString)geom).getCoordinates();
350
                }else if(geom.getGeometryType().equalsIgnoreCase("Polygon") ){
351
                        Polygon polygon = (Polygon) geom;
352
                        LineString shell = polygon.getExteriorRing();
353
                        LinearRing snapedShell = (LinearRing) snap(shell);
354
                        int numHoles = polygon.getNumInteriorRing();
355
                        LinearRing[] newHoles = new LinearRing[numHoles];
356
                        for(int i = 0; i < numHoles; i++){
357
                                LineString hole = polygon.getInteriorRingN(i);
358
                                newHoles[i] = (LinearRing) snap(hole);
359
                        }
360
                        return JtsUtil.GEOMETRY_FACTORY.createPolygon(snapedShell, newHoles);
361
                }
362
                
363
                SnapCoordinateList snapCoordList = 
364
                        new SnapCoordinateList(coords, this.snapTolerance);
365
                Coordinate[] snappedCoords = snapCoordList.toCoordinateArray();
366
                try{
367
                        solution =  JtsUtil.createGeometry(snappedCoords, 
368
                                                                        geom.getGeometryType());
369
                        if((solution.getLength() < this.snapTolerance))
370
                                throw new GeometryCollapsedException("Geometria lineal colapsada al aplicar snap");
371
                        
372
                        if(solution.getDimension() > 1 && (solution.getArea() < snapTolerance * snapTolerance))
373
                                throw new GeometryCollapsedException("Area colapsada a linea al aplicar snap");
374
                        
375
                        //FIXME The last check if verify if the solution is a JTS valid geometry
376
                        //but to do that we must check this with original geometry
377
                        if(!solution.isValid())
378
                                throw new GeometryCollapsedException("La geometria resultante no es una geometria valida");
379
                }catch(IllegalArgumentException e){
380
                        throw new GeometryCollapsedException("JTS no es capaz de construir geometria al snapear sus coordenadas", e);
381
                }
382
                return solution;
383
        }
384
        
385
        /**
386
         * Creates a geometry of the same type as geom with newPts coordinates.
387
         * geom mustn be a GeometryCollection (MultiPolygon, etc.)
388
         * @param newPts
389
         * @param geom
390
         * @return
391
         */
392
        
393
        //TODO See how to manage the snap of polygon holes
394
        
395

    
396
        public Coordinate snapAverage(List<Coordinate> coordinates, 
397
                                                                                                int[] weights){
398
                List<SnapCoordinate> snapList = new ArrayList<SnapCoordinate>();
399
                for(int i = 0; i < coordinates.size(); i++){
400
                        Coordinate coord = coordinates.get(i);
401
                        int weight = weights[i];
402
                        SnapCoordinate snap = new SnapCoordinate();
403
                        snap.weight = weight;
404
                        snap.coordinate = coord;
405
                        
406
                        snapList.add(snap);
407
                }
408
                
409
                return snapAverage(snapList);
410
        }
411
        
412
        
413
        private Coordinate snapAverage(List<SnapCoordinate> coordsToSnap){
414
                
415
                if(coordsToSnap.size() == 1)
416
                {
417
                        return coordsToSnap.get(0).coordinate;
418
                }
419
                
420
                double sumX = 0d;
421
                double sumY = 0d;
422
                double sumW = 0d;
423
                
424
                for(int i = 0; i < coordsToSnap.size(); i++){
425
                        SnapCoordinate snapCoord = coordsToSnap.get(i);
426
                        Coordinate coord = snapCoord.coordinate;
427
                        int weight = snapCoord.weight;
428
                        if(weight == 0)
429
                                continue;
430
                        
431
                        double weightD = 1d / (double)weight;
432
                        sumX += weightD * coord.x;
433
                        sumY += weightD * coord.y;
434
                        sumW += weightD;
435
                }
436
                
437
                double newX = sumX / sumW;
438
                double newY = sumY / sumW;
439
                
440
                return new Coordinate(newX, newY);
441
        }
442
                
443
        
444
        /**
445
         * If geometry 'a' is not exactly closed, but its extremes are 
446
         * in a snap radius, its snap the last point to the first point of the
447
         * geometry
448
         * @param a
449
         * @return
450
         */
451
        public Geometry normalizeExtremes(Geometry a){
452
                Coordinate[] coords = a.getCoordinates();
453
                Coordinate firstPoint = coords[0];
454
                Coordinate lastPoint = coords[coords.length - 1];
455
                if(!firstPoint.equals2D(lastPoint) && JtsUtil.isClosed(a, snapTolerance)){
456
                    CoordinateList coordList = new CoordinateList(coords);
457
                    coordList.set(coords.length - 1, coords[0]);
458
//                    return createGeometry(coordList.toCoordinateArray(), a);
459
                    return JtsUtil.createGeometry(coordList.toCoordinateArray(), 
460
                                    a.getGeometryType());
461
                }else
462
                        return a;
463
        }
464
        
465
        
466
        /**
467
         * Snaps coordinates in srcPts with coordinates of snapPts.
468
         * This method forces the snap: the points of srcPoints are shifted to
469
         * snap with points in snapPts. No average is computed for coordinates.
470
         * @param srcPts
471
         * @param isClosed
472
         * @param snapPts
473
         * @return
474
         */
475

    
476
        private Coordinate[] snapTo(Coordinate[] srcPts, 
477
                                                        boolean isClosed,
478
                                                        Coordinate[] snapPts) {
479

    
480
                CoordinateList coordList = new CoordinateList(srcPts);
481
                int coordinateCount = coordList.size();
482
                if(isClosed)
483
                        coordinateCount --;
484
                for (int i = 0; i < coordinateCount; i++) {
485
                        Coordinate srcPt = (Coordinate) coordList.get(i);
486
                        Coordinate snapVert = findSnapForVertex(srcPt, snapPts);
487
                        if (snapVert != null) {
488
                                coordList.set(i, new Coordinate(snapVert));
489
                                if (i == 0 && isClosed)
490
                                        coordList.set(coordList.size() - 1,
491
                                                                new Coordinate(snapVert));
492
                        }//if
493
                }//for
494

    
495
                Coordinate[] newPts = coordList.toCoordinateArray();
496
                return newPts;
497
        }
498

    
499
        
500

    
501
        private Coordinate findSnapForVertex(Coordinate pt, Coordinate[] snapPts) {
502
                for (int i = 0; i < snapPts.length; i++) {
503
                        if (pt.equals2D(snapPts[i]))
504
                                return null;
505
                        if(SnapCGAlgorithms.snapEquals2D(pt, snapPts[i], snapTolerance)){
506
                                return snapPts[i];
507
                        }
508
                }
509
                return null;
510
        }
511
}