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 |
} |