/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.shapefile.shp; import org.geotools.factory.Hints; import org.opengis.feature.type.GeometryDescriptor; import com.vividsolutions.jts.algorithm.CGAlgorithms; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; /** * A collection of utility methods for use with JTS and the shapefile package. * * @author aaime * @author Ian Schneider * * @source $URL$ */ public class JTSUtilities { static final CGAlgorithms cga = new CGAlgorithms(); private JTSUtilities() { } /** * Determine the min and max "z" values in an array of Coordinates. * * @param cs * The array to search. * @return An array of size 2, index 0 is min, index 1 is max. * @deprecated use zMinMax(CoordinateSequence) */ public static final double[] zMinMax(final Coordinate[] cs) { double []result = {Double.NaN, Double.NaN}; zMinMax(new CoordinateArraySequence(cs), result); return result; } /** * Determine the min and max "z" values in an array of Coordinates. * * @param cs * The array to search. * @param target * array with at least two elements where to hold the min and max * zvalues. target[0] will be filled with the minimum zvalue, * target[1] with the maximum. The array current values, if not * NaN, will be taken into acount in the computation. */ public static final void zMinMax(final CoordinateSequence cs, double[] target) { if (cs.getDimension() < 3) { return; } double zmin; double zmax; boolean validZFound = false; zmin = Double.NaN; zmax = Double.NaN; double z; final int size = cs.size(); for (int t = size - 1; t >= 0; t--) { z = cs.getOrdinate(t, 2); if (!(Double.isNaN(z))) { if (validZFound) { if (z < zmin) { zmin = z; } if (z > zmax) { zmax = z; } } else { validZFound = true; zmin = z; zmax = z; } } } if(!Double.isNaN(zmin)){ target[0] = zmin; } if(!Double.isNaN(zmax)){ target[1] = (zmax); } } /** * Determine the best ShapeType for a given Geometry. * * @param geom * The Geometry to analyze. * @return The best ShapeType for the Geometry. */ public static final ShapeType findBestGeometryType(Geometry geom) { ShapeType type = ShapeType.UNDEFINED; if (geom instanceof Point) { type = ShapeType.POINT; } else if (geom instanceof MultiPoint) { type = ShapeType.MULTIPOINT; } else if (geom instanceof Polygon) { type = ShapeType.POLYGON; } else if (geom instanceof MultiPolygon) { type = ShapeType.POLYGON; } else if (geom instanceof LineString) { type = ShapeType.ARC; } else if (geom instanceof MultiLineString) { type = ShapeType.ARC; } return type; } public static final Class findBestGeometryClass(ShapeType type) { Class best; if (type == null || type == ShapeType.NULL) { best = Geometry.class; } else if (type.isLineType()) { best = MultiLineString.class; } else if (type.isMultiPointType()) { best = MultiPoint.class; } else if (type.isPointType()) { best = Point.class; } else if (type.isPolygonType()) { best = MultiPolygon.class; } else { throw new RuntimeException("Unknown ShapeType->GeometryClass : " + type); } return best; } /** * Does what it says, reverses the order of the Coordinates in the ring. * <p> * This is different then lr.reverses() in that a copy is produced using a * new coordinate sequence. * </p> * @param lr * The ring to reverse. * @return A new ring with the reversed Coordinates. */ public static final LinearRing reverseRing(LinearRing lr) { GeometryFactory gf = lr.getFactory(); CoordinateSequenceFactory csf = gf.getCoordinateSequenceFactory(); CoordinateSequence csOrig = lr.getCoordinateSequence(); int numPoints = csOrig.size(); int dimensions = csOrig.getDimension(); CoordinateSequence csNew = csf.create(numPoints, dimensions); for (int i = 0; i < numPoints; i++) { for (int j = 0; j < dimensions; j++) { csNew.setOrdinate(numPoints-1-i, j, csOrig.getOrdinate(i, j)); } } return gf.createLinearRing(csNew); } /** * Create a nice Polygon from the given Polygon. Will ensure that shells are * clockwise and holes are counter-clockwise. Capiche? * * @param p * The Polygon to make "nice". * @return The "nice" Polygon. */ public static final Polygon makeGoodShapePolygon(Polygon p) { GeometryFactory factory = p.getFactory(); LinearRing outer; LinearRing[] holes = new LinearRing[p.getNumInteriorRing()]; Coordinate[] coords; coords = p.getExteriorRing().getCoordinates(); if (CGAlgorithms.isCCW(coords)) { outer = reverseRing((LinearRing) p.getExteriorRing()); } else { outer = (LinearRing) p.getExteriorRing(); } for (int t = 0, tt = p.getNumInteriorRing(); t < tt; t++) { coords = p.getInteriorRingN(t).getCoordinates(); if (!(CGAlgorithms.isCCW(coords))) { holes[t] = reverseRing((LinearRing) p.getInteriorRingN(t)); } else { holes[t] = (LinearRing) p.getInteriorRingN(t); } } return factory.createPolygon(outer, holes); } /** * Like makeGoodShapePolygon, but applied towards a multi polygon. * * @param mp * The MultiPolygon to "niceify". * @return The "nicified" MultiPolygon. */ public static final MultiPolygon makeGoodShapeMultiPolygon(MultiPolygon mp) { MultiPolygon result; Polygon[] ps = new Polygon[mp.getNumGeometries()]; // check each sub-polygon for (int t = 0; t < mp.getNumGeometries(); t++) { ps[t] = makeGoodShapePolygon((Polygon) mp.getGeometryN(t)); } result = mp.getFactory().createMultiPolygon(ps); return result; } /** * Returns: <br> * 2 for 2d (default) <br> * 4 for 3d - one of the oordinates has a non-NaN z value <br> * (3 is for x,y,m but thats not supported yet) <br> * * @param cs * The array of Coordinates to search. * @return The dimension. */ public static final int guessCoorinateDims(final Coordinate[] cs) { int dims = 2; for (int t = cs.length - 1; t >= 0; t--) { if (!(Double.isNaN(cs[t].z))) { dims = 4; break; } } return dims; } public static Geometry convertToCollection(Geometry geom, ShapeType type) { Geometry retVal = null; if(geom == null) return null; GeometryFactory factory = geom.getFactory(); if (type.isPointType()) { if ((geom instanceof Point)) { retVal = geom; } else { Point[] pNull = null; retVal = factory.createMultiPoint(pNull); } } else if (type.isLineType()) { if ((geom instanceof LineString)) { retVal = factory .createMultiLineString(new LineString[] { (LineString) geom }); } else if (geom instanceof MultiLineString) { retVal = geom; } else { retVal = factory.createMultiLineString(null); } } else if (type.isPolygonType()) { if (geom instanceof Polygon) { Polygon p = makeGoodShapePolygon((Polygon) geom); retVal = factory.createMultiPolygon(new Polygon[] { p }); } else if (geom instanceof MultiPolygon) { retVal = JTSUtilities .makeGoodShapeMultiPolygon((MultiPolygon) geom); } else { retVal = factory.createMultiPolygon(null); } } else if (type.isMultiPointType()) { if ((geom instanceof Point)) { retVal = factory.createMultiPoint(new Point[] { (Point) geom }); } else if (geom instanceof MultiPoint) { retVal = geom; } else { Point[] pNull = null; retVal = factory.createMultiPoint(pNull); } } else throw new RuntimeException("Could not convert " + geom.getClass() + " to " + type); return retVal; } /** * Determine the best ShapeType for a geometry with the given dimension. * * @param geom * The Geometry to examine. * @param shapeFileDimentions * The dimension 2,3 or 4. * @throws ShapefileException * If theres a problem, like a bogus Geometry. * @return The best ShapeType. */ public static final ShapeType getShapeType(Geometry geom, int shapeFileDimentions) throws ShapefileException { ShapeType type = null; if (geom instanceof Point) { switch (shapeFileDimentions) { case 2: type = ShapeType.POINT; break; case 3: type = ShapeType.POINTM; break; case 4: type = ShapeType.POINTZ; break; default: throw new ShapefileException( "Too many dimensions for shapefile : " + shapeFileDimentions); } } else if (geom instanceof MultiPoint) { switch (shapeFileDimentions) { case 2: type = ShapeType.MULTIPOINT; break; case 3: type = ShapeType.MULTIPOINTM; break; case 4: type = ShapeType.MULTIPOINTZ; break; default: throw new ShapefileException( "Too many dimensions for shapefile : " + shapeFileDimentions); } } else if ((geom instanceof Polygon) || (geom instanceof MultiPolygon)) { switch (shapeFileDimentions) { case 2: type = ShapeType.POLYGON; break; case 3: type = ShapeType.POLYGONM; break; case 4: type = ShapeType.POLYGONZ; break; default: throw new ShapefileException( "Too many dimensions for shapefile : " + shapeFileDimentions); } } else if ((geom instanceof LineString) || (geom instanceof MultiLineString)) { switch (shapeFileDimentions) { case 2: type = ShapeType.ARC; break; case 3: type = ShapeType.ARCM; break; case 4: type = ShapeType.ARCZ; break; default: throw new ShapefileException( "Too many dimensions for shapefile : " + shapeFileDimentions); } } if (type == null) { throw new ShapefileException("Cannot handle geometry type : " + (geom == null ? "null" : geom.getClass().getName())); } return type; } /** * Determine the default ShapeType for a featureClass. Shapetype will be the * 2D shapetype. * * @param featureClass * The Geometry to examine. * @return The best ShapeType. * @throws ShapefileException * If theres a problem, like a bogus feature class. */ public static final ShapeType getShapeType(Class featureClass) throws ShapefileException { ShapeType type = null; if (Point.class.equals(featureClass)) { type = ShapeType.POINT; } else if (MultiPoint.class.equals(featureClass)) { type = ShapeType.MULTIPOINT; } else if (Polygon.class.equals(featureClass) || MultiPolygon.class.equals(featureClass)) { type = ShapeType.POLYGON; } else if (LineString.class.equals(featureClass) || MultiLineString.class.equals(featureClass)) { type = ShapeType.ARC; } if (type == null) { throw new ShapefileException("Cannot handle geometry class : " + (featureClass == null ? "null" : featureClass.getName())); } return type; } /** * Determine the default ShapeType using the descriptor and eventually the * geometry to guess the coordinate dimensions if not reported in the descriptor * hints * @param gd * @param g * @return */ public static final ShapeType getShapeType(GeometryDescriptor gd) throws ShapefileException { Class featureClass = gd.getType().getBinding(); Integer dimension = (Integer) gd.getUserData().get(Hints.COORDINATE_DIMENSION); ShapeType type = null; if (Point.class.equals(featureClass)) { if(dimension != null && dimension == 3) type = ShapeType.POINTZ; else type = ShapeType.POINT; } else if (MultiPoint.class.equals(featureClass)) { if(dimension != null && dimension == 3) type = ShapeType.MULTIPOINTZ; else type = ShapeType.MULTIPOINT; } else if (Polygon.class.equals(featureClass) || MultiPolygon.class.equals(featureClass)) { if(dimension != null && dimension == 3) type = ShapeType.POLYGON; else type = ShapeType.POLYGONZ; } else if (LineString.class.equals(featureClass) || MultiLineString.class.equals(featureClass)) { if(dimension != null && dimension == 3) type = ShapeType.ARC; else type = ShapeType.ARCZ; } if (type == null) { throw new ShapefileException("Cannot handle geometry class : " + (featureClass == null ? "null" : featureClass.getName())); } return type; } }