/* * Copyright (c) 2016 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.util.geometry; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.AxisDirection; import com.vividsolutions.jts.algorithm.CGAlgorithms; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Polygon; /** * Winding order changer for Geometry objects. * * @author Arun */ public class WindingOrder { private static final GeometryFactory factory = new GeometryFactory(); /** * To determine axis of given CRS is flip or not * * @param crs the CRS * @return true, if CRS is flip, else false */ public static boolean isCRSFlip(CoordinateReferenceSystem crs) { // get number of dimensions and it should be two if (crs.getCoordinateSystem().getDimension() == 2) { AxisDirection axis1 = crs.getCoordinateSystem().getAxis(0).getDirection(); AxisDirection axis2 = crs.getCoordinateSystem().getAxis(1).getDirection(); // check if direction is flip? if (axis1.equals(AxisDirection.EAST) || axis1.equals(AxisDirection.WEST)) { if (axis1.equals(AxisDirection.EAST) && (axis2.equals(AxisDirection.SOUTH) || axis2.equals(AxisDirection.DOWN))) { return true; } else if (axis1.equals(AxisDirection.WEST) && (axis2.equals(AxisDirection.NORTH) || axis2.equals(AxisDirection.UP))) { return true; } } else { // Check if order of axis flip? if ((axis1.equals(AxisDirection.SOUTH) || axis1.equals(AxisDirection.DOWN)) && axis2.equals(AxisDirection.WEST)) { return true; } else if ((axis1.equals(AxisDirection.NORTH) || axis1.equals(AxisDirection.UP)) && axis2.equals(AxisDirection.EAST)) { return true; } } } // if not any case return false above. return false; } /** * Identifying order of Geometry object CounterClockwise or not? * * @param geometry Geometry object to identify order. * @return boolean value, true, if order is counterclockwise, else false if * order is clockwise. */ public static boolean isCounterClockwise(Geometry geometry) { if (geometry == null || geometry.isEmpty()) return false; // Get coordinates of geometry Coordinate[] coordinates = geometry.getCoordinates(); if (coordinates.length < 3) { // orientation cannot be determined // but this should also not happen for linear rings return false; } // Get order of geometry using algorithm boolean orientation = false; orientation = CGAlgorithms.isCCW(coordinates); return orientation; } /** * Unify Winding order of Geometry object( including Polygon, Multipolygon, * LinearRing) as CounterClockwise or Clockwise. * * @param geometry Geometry object for unifying. * @param counterClockWise true, if unify geometry counterClockwise else * false. * @param crs Coordinate Reference System * @return Geometry unified object. */ public static Geometry unifyWindingOrder(Geometry geometry, boolean counterClockWise, CoordinateReferenceSystem crs) { if (geometry == null) { return null; } if (geometry.isEmpty()) { return geometry; } // Getting isCRS flip? if yes, then reverse the winding order of input if (crs != null && isCRSFlip(crs)) counterClockWise = !counterClockWise; if (geometry instanceof MultiPolygon) { return unifyWindingOrderForMultiPolygon((MultiPolygon) geometry, counterClockWise); } else if (geometry instanceof Polygon) { return unifyWindingOrderForPolyGon((Polygon) geometry, counterClockWise); } else if (geometry instanceof LinearRing) { return unifyWindingOrderForLinearRing((LinearRing) geometry, counterClockWise); } else if (GeometryCollection.class.equals(geometry.getClass())) { return unifyWindingOrderForGeometryCollection((GeometryCollection) geometry, counterClockWise); } else return geometry; } /** * Unify order for LinearRing as CounterClockwise or Clockwise. * * @param linearRing LinearRing object for unifying * @param counterClockWise boolean value. true, if want geometry as counter * clock wise, else false. * @return LinearRing unified object. */ public static LinearRing unifyWindingOrderForLinearRing(LinearRing linearRing, boolean counterClockWise) { // Checking and reversing geometry if (isCounterClockwise(linearRing) == counterClockWise) return linearRing; else return (LinearRing) linearRing.reverse(); } /** * Unify order of the polygon as counterClockwise or not including all its * holes. * * @param poly Polygon object for unifying * @param counterClockWise boolean value. true, if want shell of Polygon as * counter clock wise and holes as clockwise, else false. * @return Polygon unified object. */ public static Polygon unifyWindingOrderForPolyGon(Polygon poly, boolean counterClockWise) { // Checking and reversing Shell LinearRing shell = unifyWindingOrderForLinearRing((LinearRing) poly.getExteriorRing(), counterClockWise); Polygon revPoly; // Checking and reversing Holes if (poly.getNumInteriorRing() > 0) { LinearRing holes[] = new LinearRing[poly.getNumInteriorRing()]; for (int i = 0; i < poly.getNumInteriorRing(); i++) { holes[i] = unifyWindingOrderForLinearRing((LinearRing) poly.getInteriorRingN(i), !counterClockWise); } // Create New Polygon using unified shell and holes both. revPoly = factory.createPolygon(shell, holes); } else // Create New Polygon using unified shell only revPoly = factory.createPolygon(shell); return revPoly; } /** * Unify order of the Multipolygon including all polygons and holes in it. * * @param multiPoly Multipolygon object for unifying it. * @param counterClockWise boolean value. true, if want shell of * multipolygon as counter clock wise and holes as clockwise, * else false. * @return Multipolygon unified Object */ public static MultiPolygon unifyWindingOrderForMultiPolygon(MultiPolygon multiPoly, boolean counterClockWise) { // get no of polygons in MultiPolygon int noOfPolygons = multiPoly.getNumGeometries(); Polygon[] revGeoms = new Polygon[noOfPolygons]; // Unify each inner polygon one by one for (int i = 0; i < noOfPolygons; i++) { revGeoms[i] = unifyWindingOrderForPolyGon((Polygon) multiPoly.getGeometryN(i), counterClockWise); } // new multipolygon of unified polygons return factory.createMultiPolygon(revGeoms); } /** * Unify order of the GeometryCollection including all Geometries in it. * * @param geoCollection GeometryCollection object for unifying. * @param counterClockWise boolean value. true, if want all geometry object * * as counter clock wise, else false. * @return GeometryCollection unified Object */ public static GeometryCollection unifyWindingOrderForGeometryCollection( GeometryCollection geoCollection, boolean counterClockWise) { // get no of geometries in GeometryCollection int noOfGeoms = geoCollection.getNumGeometries(); Geometry[] revGeoms = new Geometry[noOfGeoms]; // Unify each geometry one by one for (int i = 0; i < noOfGeoms; i++) { revGeoms[i] = unifyWindingOrder(geoCollection.getGeometryN(i), counterClockWise, null); } // new geolmetry collection object of unified geometry objects. return factory.createGeometryCollection(revGeoms); } }