001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2003, Centre for Computational Geography
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * Created on March 5, 2003, 11:18 AM
018: */
019: package org.geotools.data.shapefile.shp;
020:
021: import com.vividsolutions.jts.algorithm.CGAlgorithms;
022: import com.vividsolutions.jts.algorithm.RobustCGAlgorithms;
023: import com.vividsolutions.jts.geom.Coordinate;
024: import com.vividsolutions.jts.geom.Geometry;
025: import com.vividsolutions.jts.geom.GeometryFactory;
026: import com.vividsolutions.jts.geom.LineString;
027: import com.vividsolutions.jts.geom.LinearRing;
028: import com.vividsolutions.jts.geom.MultiLineString;
029: import com.vividsolutions.jts.geom.MultiPoint;
030: import com.vividsolutions.jts.geom.MultiPolygon;
031: import com.vividsolutions.jts.geom.Point;
032: import com.vividsolutions.jts.geom.Polygon;
033:
034: /** A collection of utility methods for use with JTS and the shapefile package.
035: * @author aaime
036: * @author Ian Schneider
037: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/shp/JTSUtilities.java $
038: */
039: public class JTSUtilities {
040:
041: static final CGAlgorithms cga = new RobustCGAlgorithms();
042: static final GeometryFactory factory = new GeometryFactory();
043:
044: private JTSUtilities() {
045: }
046:
047: /** Determine the min and max "z" values in an array of Coordinates.
048: * @param cs The array to search.
049: * @return An array of size 2, index 0 is min, index 1 is max.
050: */
051: public static final double[] zMinMax(final Coordinate[] cs) {
052: double zmin;
053: double zmax;
054: boolean validZFound = false;
055: double[] result = new double[2];
056:
057: zmin = Double.NaN;
058: zmax = Double.NaN;
059:
060: double z;
061:
062: for (int t = cs.length - 1; t >= 0; t--) {
063: z = cs[t].z;
064:
065: if (!(Double.isNaN(z))) {
066: if (validZFound) {
067: if (z < zmin) {
068: zmin = z;
069: }
070:
071: if (z > zmax) {
072: zmax = z;
073: }
074: } else {
075: validZFound = true;
076: zmin = z;
077: zmax = z;
078: }
079: }
080: }
081:
082: result[0] = (zmin);
083: result[1] = (zmax);
084:
085: return result;
086: }
087:
088: /** Determine the best ShapeType for a given Geometry.
089: * @param geom The Geometry to analyze.
090: * @return The best ShapeType for the Geometry.
091: */
092: public static final ShapeType findBestGeometryType(Geometry geom) {
093: ShapeType type = ShapeType.UNDEFINED;
094:
095: if (geom instanceof Point) {
096: type = ShapeType.POINT;
097: } else if (geom instanceof MultiPoint) {
098: type = ShapeType.MULTIPOINT;
099: } else if (geom instanceof Polygon) {
100: type = ShapeType.POLYGON;
101: } else if (geom instanceof MultiPolygon) {
102: type = ShapeType.POLYGON;
103: } else if (geom instanceof LineString) {
104: type = ShapeType.ARC;
105: } else if (geom instanceof MultiLineString) {
106: type = ShapeType.ARC;
107: }
108: return type;
109: }
110:
111: public static final Class findBestGeometryClass(ShapeType type) {
112: Class best;
113: if (type == null || type == ShapeType.NULL) {
114: best = Geometry.class;
115: } else if (type.isLineType()) {
116: best = MultiLineString.class;
117: } else if (type.isMultiPointType()) {
118: best = MultiPoint.class;
119: } else if (type.isPointType()) {
120: best = Point.class;
121: } else if (type.isPolygonType()) {
122: best = MultiPolygon.class;
123: } else {
124: throw new RuntimeException(
125: "Unknown ShapeType->GeometryClass : " + type);
126: }
127: return best;
128: }
129:
130: /** Does what it says, reverses the order of the Coordinates in the ring.
131: * @param lr The ring to reverse.
132: * @return A new ring with the reversed Coordinates.
133: */
134: public static final LinearRing reverseRing(LinearRing lr) {
135: int numPoints = lr.getNumPoints() - 1;
136: Coordinate[] newCoords = new Coordinate[numPoints + 1];
137:
138: for (int t = numPoints; t >= 0; t--) {
139: newCoords[t] = lr.getCoordinateN(numPoints - t);
140: }
141:
142: return factory.createLinearRing(newCoords);
143: }
144:
145: /** Create a nice Polygon from the given Polygon. Will ensure that shells are
146: * clockwise and holes are counter-clockwise. Capiche?
147: * @param p The Polygon to make "nice".
148: * @return The "nice" Polygon.
149: */
150: public static final Polygon makeGoodShapePolygon(Polygon p) {
151: LinearRing outer;
152: LinearRing[] holes = new LinearRing[p.getNumInteriorRing()];
153: Coordinate[] coords;
154:
155: coords = p.getExteriorRing().getCoordinates();
156:
157: if (CGAlgorithms.isCCW(coords)) {
158: outer = reverseRing((LinearRing) p.getExteriorRing());
159: } else {
160: outer = (LinearRing) p.getExteriorRing();
161: }
162:
163: for (int t = 0, tt = p.getNumInteriorRing(); t < tt; t++) {
164: coords = p.getInteriorRingN(t).getCoordinates();
165:
166: if (!(CGAlgorithms.isCCW(coords))) {
167: holes[t] = reverseRing((LinearRing) p
168: .getInteriorRingN(t));
169: } else {
170: holes[t] = (LinearRing) p.getInteriorRingN(t);
171: }
172: }
173:
174: return factory.createPolygon(outer, holes);
175: }
176:
177: /** Like makeGoodShapePolygon, but applied towards a multi polygon.
178: * @param mp The MultiPolygon to "niceify".
179: * @return The "nicified" MultiPolygon.
180: */
181: public static final MultiPolygon makeGoodShapeMultiPolygon(
182: MultiPolygon mp) {
183: MultiPolygon result;
184: Polygon[] ps = new Polygon[mp.getNumGeometries()];
185:
186: //check each sub-polygon
187: for (int t = 0; t < mp.getNumGeometries(); t++) {
188: ps[t] = makeGoodShapePolygon((Polygon) mp.getGeometryN(t));
189: }
190:
191: result = factory.createMultiPolygon(ps);
192:
193: return result;
194: }
195:
196: /** Returns: <br>
197: * 2 for 2d (default) <br>
198: * 4 for 3d - one of the oordinates has a non-NaN z value <br>
199: * (3 is for x,y,m but thats not supported yet) <br>
200: * @param cs The array of Coordinates to search.
201: * @return The dimension.
202: */
203: public static final int guessCoorinateDims(final Coordinate[] cs) {
204: int dims = 2;
205:
206: for (int t = cs.length - 1; t >= 0; t--) {
207: if (!(Double.isNaN(cs[t].z))) {
208: dims = 4;
209: break;
210: }
211: }
212:
213: return dims;
214: }
215:
216: public static Geometry convertToCollection(Geometry geom,
217: ShapeType type) {
218: Geometry retVal = null;
219:
220: if (type.isPointType()) {
221: if ((geom instanceof Point)) {
222: retVal = geom;
223: } else {
224: Point[] pNull = null;
225: retVal = factory.createMultiPoint(pNull);
226: }
227: } else if (type.isLineType()) {
228: if ((geom instanceof LineString)) {
229: retVal = factory
230: .createMultiLineString(new LineString[] { (LineString) geom });
231: } else if (geom instanceof MultiLineString) {
232: retVal = geom;
233: } else {
234: retVal = factory.createMultiLineString(null);
235: }
236: } else if (type.isPolygonType()) {
237: if (geom instanceof Polygon) {
238: Polygon p = makeGoodShapePolygon((Polygon) geom);
239: retVal = factory
240: .createMultiPolygon(new Polygon[] { p });
241: } else if (geom instanceof MultiPolygon) {
242: retVal = JTSUtilities
243: .makeGoodShapeMultiPolygon((MultiPolygon) geom);
244: } else {
245: retVal = factory.createMultiPolygon(null);
246: }
247: } else if (type.isMultiPointType()) {
248: if ((geom instanceof Point)) {
249: retVal = factory
250: .createMultiPoint(new Point[] { (Point) geom });
251: } else if (geom instanceof MultiPoint) {
252: retVal = geom;
253: } else {
254: Point[] pNull = null;
255: retVal = factory.createMultiPoint(pNull);
256: }
257: } else
258: throw new RuntimeException("Could not convert "
259: + geom.getClass() + " to " + type);
260:
261: return retVal;
262: }
263:
264: /** Determine the best ShapeType for a geometry with the given dimension.
265: * @param geom The Geometry to examine.
266: * @param shapeFileDimentions The dimension 2,3 or 4.
267: * @throws ShapefileException If theres a problem, like a bogus Geometry.
268: * @return The best ShapeType.
269: */
270: public static final ShapeType getShapeType(Geometry geom,
271: int shapeFileDimentions) throws ShapefileException {
272:
273: ShapeType type = null;
274:
275: if (geom instanceof Point) {
276: switch (shapeFileDimentions) {
277: case 2:
278: type = ShapeType.POINT;
279: break;
280: case 3:
281: type = ShapeType.POINTM;
282: break;
283: case 4:
284: type = ShapeType.POINTZ;
285: break;
286: default:
287: throw new ShapefileException(
288: "Too many dimensions for shapefile : "
289: + shapeFileDimentions);
290: }
291: } else if (geom instanceof MultiPoint) {
292: switch (shapeFileDimentions) {
293: case 2:
294: type = ShapeType.MULTIPOINT;
295: break;
296: case 3:
297: type = ShapeType.MULTIPOINTM;
298: break;
299: case 4:
300: type = ShapeType.MULTIPOINTZ;
301: break;
302: default:
303: throw new ShapefileException(
304: "Too many dimensions for shapefile : "
305: + shapeFileDimentions);
306: }
307: } else if ((geom instanceof Polygon)
308: || (geom instanceof MultiPolygon)) {
309: switch (shapeFileDimentions) {
310: case 2:
311: type = ShapeType.POLYGON;
312: break;
313: case 3:
314: type = ShapeType.POLYGONM;
315: break;
316: case 4:
317: type = ShapeType.POLYGONZ;
318: break;
319: default:
320: throw new ShapefileException(
321: "Too many dimensions for shapefile : "
322: + shapeFileDimentions);
323: }
324: } else if ((geom instanceof LineString)
325: || (geom instanceof MultiLineString)) {
326: switch (shapeFileDimentions) {
327: case 2:
328: type = ShapeType.ARC;
329: break;
330: case 3:
331: type = ShapeType.ARCM;
332: break;
333: case 4:
334: type = ShapeType.ARCZ;
335: break;
336: default:
337: throw new ShapefileException(
338: "Too many dimensions for shapefile : "
339: + shapeFileDimentions);
340: }
341: }
342:
343: if (type == null) {
344: throw new ShapefileException(
345: "Cannot handle geometry type : "
346: + (geom == null ? "null" : geom.getClass()
347: .getName()));
348: }
349: return type;
350: }
351:
352: /**
353: * Determine the default ShapeType for a featureClass. Shapetype will be the 2D shapetype.
354: *
355: * @param featureClass The Geometry to examine.
356: * @return The best ShapeType.
357: * @throws ShapefileException If theres a problem, like a bogus feature class.
358: */
359: public static final ShapeType getShapeType(Class featureClass)
360: throws ShapefileException {
361:
362: ShapeType type = null;
363:
364: if (Point.class.equals(featureClass)) {
365: type = ShapeType.POINT;
366: } else if (MultiPoint.class.equals(featureClass)) {
367: type = ShapeType.MULTIPOINT;
368: } else if (Polygon.class.equals(featureClass)
369: || MultiPolygon.class.equals(featureClass)) {
370: type = ShapeType.POLYGON;
371: } else if (LineString.class.equals(featureClass)
372: || MultiLineString.class.equals(featureClass)) {
373: type = ShapeType.ARC;
374: }
375:
376: if (type == null) {
377: throw new ShapefileException(
378: "Cannot handle geometry class : "
379: + (featureClass == null ? "null"
380: : featureClass.getName()));
381: }
382: return type;
383: }
384:
385: }
|