/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.geometry.jts; import java.util.ArrayList; import java.util.List; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; 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; /** * Service object that takes a geometry and applies a {@link MathTransform} * to the coordinates it contains, creating a new * geometry as the transformed output. * <p> * The standard usage pattern is * to supply a {@link MathTransform} and @link CoordinateReferenceSystem} explicitly. * The {@link #transform(Geometry)} method can then be * used to construct transformed geometries using the {@link GeometryFactory} * and {@link CoordinateSequenceFactory} of the input geometry. * * @author Andrea Aime * @author Martin Davis * * * @source $URL$ */ public class GeometryCoordinateSequenceTransformer { private MathTransform transform = null; private CoordinateReferenceSystem crs; private CoordinateSequenceTransformer inputCSTransformer = null; private CoordinateSequenceTransformer csTransformer = null; private GeometryFactory currGeometryFactory = null; private boolean curveCompatible; /** * Creates a transformer which uses the {@link CoordinateSequenceFactory} * of the source geometries. */ public GeometryCoordinateSequenceTransformer() { // the csTransformer is initialized from the first geometry // and the supplied transform } /** * Creates a transformer which uses a client-specified * {@link CoordinateSequenceTransformer}. * <p> * <b>WARNING:</b> The CoordinateSequenceTransformer must use * the same {@link CoordinateSequenceFactory} as the output * GeometryFactory, so that geometries are constructed consistently. * * @param transformer */ public GeometryCoordinateSequenceTransformer(CoordinateSequenceTransformer transformer) { inputCSTransformer = transformer; csTransformer = transformer; } /** * Sets the math transform to be used for transformation * @param transform */ public void setMathTransform(MathTransform transform) { this.transform = transform; this.curveCompatible = isCurveCompatible(transform); } /** * Sets the target coordinate reference system. * <p> * This value is used to set the coordinate reference system of geometries * after they have been transformed. * </p> * @param crs The target coordinate reference system. */ public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) { this.crs = crs; } /** * Initializes the internal CoordinateSequenceTransformer * if not specified explicitly. * * @param gf the factory to use */ private void init(GeometryFactory gf) { // don't init if csTransformer already exists if (inputCSTransformer != null) return; // don't reinit if gf is the same (the usual case) if (currGeometryFactory == gf) return; currGeometryFactory = gf; CoordinateSequenceFactory csf = gf.getCoordinateSequenceFactory(); csTransformer = new DefaultCoordinateSequenceTransformer(csf); } /** * Applies the transform to the provided geometry, * creating a new transformed geometry. * * @param g the geometry to transform * @return a new transformed geometry * @throws TransformException */ public Geometry transform(Geometry g) throws TransformException { GeometryFactory factory = g.getFactory(); Geometry transformed = null; // lazily init csTransformer using geometry's CSFactory init(factory); if (g instanceof Point) { transformed = transformPoint((Point) g, factory); } else if (g instanceof MultiPoint) { MultiPoint mp = (MultiPoint) g; Point[] points = new Point[mp.getNumGeometries()]; for (int i = 0; i < points.length; i++) { points[i] = transformPoint((Point) mp.getGeometryN(i), factory); } transformed = factory.createMultiPoint(points); } else if (g instanceof LineString) { transformed = transformLineString((LineString) g, factory); } else if (g instanceof MultiLineString) { MultiLineString mls = (MultiLineString) g; LineString[] lines = new LineString[mls.getNumGeometries()]; for (int i = 0; i < lines.length; i++) { lines[i] = transformLineString((LineString) mls.getGeometryN(i), factory); } transformed = factory.createMultiLineString(lines); } else if (g instanceof Polygon) { transformed = transformPolygon((Polygon) g, factory); } else if (g instanceof MultiPolygon) { MultiPolygon mp = (MultiPolygon) g; Polygon[] polygons = new Polygon[mp.getNumGeometries()]; for (int i = 0; i < polygons.length; i++) { polygons[i] = transformPolygon((Polygon) mp.getGeometryN(i), factory); } transformed = factory.createMultiPolygon(polygons); } else if (g instanceof GeometryCollection) { GeometryCollection gc = (GeometryCollection) g; Geometry[] geoms = new Geometry[gc.getNumGeometries()]; for (int i = 0; i < geoms.length; i++) { geoms[i] = transform(gc.getGeometryN(i)); } transformed = factory.createGeometryCollection(geoms); } else { throw new IllegalArgumentException("Unsupported geometry type " + g.getClass()); } //copy over user data // do a special check for coordinate reference system transformed.setUserData(g.getUserData()); if ((g.getUserData() == null) || g.getUserData() instanceof CoordinateReferenceSystem) { //set the new one to be the target crs if (crs != null) { transformed.setUserData(crs); } } return transformed; } /** * * @throws TransformException */ public LineString transformLineString(LineString ls, GeometryFactory gf) throws TransformException { if (ls instanceof CurvedGeometry<?> && curveCompatible) { return transformCurvedLineString((CurvedGeometry) ls, gf); } else { return transformStraightLineString(ls, gf); } } private boolean isCurveCompatible(MathTransform mt) { if (!(mt instanceof AffineTransform2D)) { return false; } AffineTransform2D at = (AffineTransform2D) mt; // return true if we are scaling by the same amount, and the rotational // elements are equal in absolute value return at.getScaleX() == at.getScaleY() && Math.abs(at.getShearX()) == Math.abs(at.getShearY()); } private LineString transformStraightLineString(LineString ls, GeometryFactory gf) throws TransformException { // if required, init csTransformer using geometry's CSFactory init(gf); CoordinateSequence cs = projectCoordinateSequence(ls.getCoordinateSequence()); LineString transformed = null; if (ls instanceof LinearRing) { transformed = gf.createLinearRing(cs); } else { transformed = gf.createLineString(cs); } transformed.setUserData( ls.getUserData() ); return transformed; } private LineString transformCurvedLineString(CurvedGeometry<?> curved, GeometryFactory gf) throws TransformException { CurvedGeometryFactory cf = CurvedGeometries.getFactory(curved); if (curved instanceof SingleCurvedGeometry<?>) { SingleCurvedGeometry<?> single = (SingleCurvedGeometry<?>) curved; double[] controlPoints = single.getControlPoints(); double[] target = new double[controlPoints.length]; transform.transform(controlPoints, 0, target, 0, controlPoints.length / 2); return cf.createCurvedGeometry(2, target); } else { CompoundCurvedGeometry<?> compound = (CompoundCurvedGeometry<?>) curved; List<LineString> reprojected = new ArrayList<>(); for (LineString component : compound.getComponents()) { LineString ls = transformLineString(component, gf); reprojected.add(ls); } return cf.createCurvedGeometry(reprojected); } } /** * @param point * * @throws TransformException */ public Point transformPoint(Point point, GeometryFactory gf) throws TransformException { // if required, init csTransformer using geometry's CSFactory init(gf); CoordinateSequence cs = projectCoordinateSequence(point.getCoordinateSequence()); Point transformed = gf.createPoint(cs); transformed.setUserData( point.getUserData() ); return transformed; } /** * @param cs a CoordinateSequence * * @throws TransformException */ private CoordinateSequence projectCoordinateSequence(CoordinateSequence cs) throws TransformException { return csTransformer.transform(cs, transform); } /** * @param polygon * @throws TransformException */ public Polygon transformPolygon(Polygon polygon, GeometryFactory gf) throws TransformException { LinearRing exterior = (LinearRing) transformLineString(polygon.getExteriorRing(), gf); LinearRing[] interiors = new LinearRing[polygon.getNumInteriorRing()]; for (int i = 0; i < interiors.length; i++) { interiors[i] = (LinearRing) transformLineString(polygon.getInteriorRingN(i), gf); } Polygon transformed = gf.createPolygon(exterior, interiors); transformed.setUserData( polygon.getUserData() ); return transformed; } }