All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotools.geometry.jts.JTS Maven / Gradle / Ivy

/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2005-2015, 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.awt.*;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.geotools.geometry.AbstractDirectPosition;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.GeneralDirectPosition;
import org.geotools.geometry.util.ShapeUtilities;
import org.geotools.metadata.i18n.ErrorKeys;
import org.geotools.metadata.i18n.Errors;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.TransformPathNotFoundException;
import org.geotools.referencing.operation.projection.PointOutsideEnvelopeException;
import org.geotools.util.Classes;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.awt.ShapeReader;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateSequenceFactory;
import org.locationtech.jts.geom.CoordinateSequenceFilter;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jts.operation.polygonize.Polygonizer;
import org.opengis.geometry.BoundingBox;
import org.opengis.geometry.BoundingBox3D;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.referencing.operation.TransformException;

/**
 * JTS Geometry utility methods, bringing Geotools to JTS.
 *
 * 

Offers geotools based services such as reprojection. * *

Responsibilities: * *

    *
  • transformation *
  • coordinate sequence editing *
  • common coordinate sequence implementations for specific uses *
* * @since 2.2 * @version $Id$ * @author Jody Garnett * @author Martin Desruisseaux * @author Simone Giannecchini, GeoSolutions. * @author Michael Bedward */ public final class JTS { /** A pool of direct positions for use in {@link #orthodromicDistance}. */ private static final GeneralDirectPosition[] POSITIONS = new GeneralDirectPosition[4]; public static final AffineTransformation Y_INVERSION = new AffineTransformation(1, 0, 0, 0, -1, 0); static { for (int i = 0; i < POSITIONS.length; i++) { POSITIONS[i] = new GeneralDirectPosition(i); } } /** * Geodetic calculators already created for a given coordinate reference system. For use in * {@link #orthodromicDistance}. * *

Note: We would like to use {@link org.geotools.util.CanonicalSet}, but we can't because * {@link GeodeticCalculator} keep a reference to the CRS which is used as the key. */ private static final Map CALCULATORS = new HashMap(); /** Do not allow instantiation of this class. */ private JTS() {} /** * Makes sure that an argument is non-null. * * @param name Argument name. * @param object User argument. * @throws IllegalArgumentException if {@code object} is null. */ private static void ensureNonNull(final String name, final Object object) throws IllegalArgumentException { if (object == null) { throw new IllegalArgumentException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, name)); } } /** * Transforms the envelope using the specified math transform. Note that this method can not * handle the case where the envelope contains the North or South pole, or when it cross the * ±180� longitude, because {@linkplain MathTransform math transforms} do not carry * suffisient informations. For a more robust envelope transformation, use {@link * ReferencedEnvelope#transform(CoordinateReferenceSystem, boolean)} instead. * * @param envelope The envelope to transform. * @param transform The transform to use. * @return The transformed Envelope * @throws TransformException if at least one coordinate can't be transformed. */ public static Envelope transform(final Envelope envelope, final MathTransform transform) throws TransformException { return transform(envelope, null, transform, 5); } /** * Transforms the densified envelope using the specified math transform. The envelope is * densified (extra points put around the outside edge) to provide a better new envelope for * high deformed situations. * *

If an optional target envelope is provided, this envelope will be {@linkplain * Envelope#expandToInclude expanded} with the transformation result. It will * not be {@linkplain Envelope#setToNull nullified} before the expansion. * *

Note that this method can not handle the case where the envelope contains the North or * South pole, or when it cross the ±180� longitude, because {@linkplain MathTransform * math transforms} do not carry suffisient informations. For a more robust envelope * transformation, use {@link ReferencedEnvelope#transform(CoordinateReferenceSystem, boolean, * int)} instead. * * @param sourceEnvelope The envelope to transform. * @param targetEnvelope An envelope to expand with the transformation result, or {@code null} * for returning an new envelope. * @param transform The transform to use. * @param npoints Densification of each side of the rectangle. * @return {@code targetEnvelope} if it was non-null, or a new envelope otherwise. In all case, * the returned envelope fully contains the transformed envelope. * @throws TransformException if a coordinate can't be transformed. */ public static Envelope transform( final Envelope sourceEnvelope, Envelope targetEnvelope, final MathTransform transform, int npoints) throws TransformException { ensureNonNull("sourceEnvelope", sourceEnvelope); ensureNonNull("transform", transform); if (transform.getSourceDimensions() != transform.getTargetDimensions() || transform.getSourceDimensions() < 2) { throw new MismatchedDimensionException( Errors.format( ErrorKeys.BAD_TRANSFORM_$1, Classes.getShortClassName(transform))); } npoints++; // for the starting point. final double[] coordinates = new double[(4 * npoints) * 2]; final double xmin = sourceEnvelope.getMinX(); final double xmax = sourceEnvelope.getMaxX(); final double ymin = sourceEnvelope.getMinY(); final double ymax = sourceEnvelope.getMaxY(); final double scaleX = (xmax - xmin) / npoints; final double scaleY = (ymax - ymin) / npoints; int offset = 0; for (int t = 0; t < npoints; t++) { final double dx = scaleX * t; final double dy = scaleY * t; coordinates[offset++] = xmin; // Left side, increasing toward top. coordinates[offset++] = ymin + dy; coordinates[offset++] = xmin + dx; // Top side, increasing toward right. coordinates[offset++] = ymax; coordinates[offset++] = xmax; // Right side, increasing toward bottom. coordinates[offset++] = ymax - dy; coordinates[offset++] = xmax - dx; // Bottom side, increasing toward left. coordinates[offset++] = ymin; } assert offset == coordinates.length; xform(transform, coordinates, coordinates); // Now find the min/max of the result if (targetEnvelope == null) { targetEnvelope = new Envelope(); } for (int t = 0; t < offset; ) { targetEnvelope.expandToInclude(coordinates[t++], coordinates[t++]); } return targetEnvelope; } /** * Transform from D up to 3D. * *

This method transforms each ordinate into WGS84, manually converts this to WGS84_3D with * the addition of a Double.NaN, and then transforms to the final 3D position. * * @return ReferencedEnvelope3D in targetCRS describing the sourceEnvelope bounds * @throws FactoryException If operationis unavailable from source CRS to WGS84, to from * WGS84_3D to targetCRS */ // JTS.transformUp(this, targetCRS, numPointsForTransformation ); public static ReferencedEnvelope3D transformTo3D( final ReferencedEnvelope sourceEnvelope, CoordinateReferenceSystem targetCRS, boolean lenient, int npoints) throws TransformException, OperationNotFoundException, FactoryException { final double xmin = sourceEnvelope.getMinX(); final double xmax = sourceEnvelope.getMaxX(); final double ymin = sourceEnvelope.getMinY(); final double ymax = sourceEnvelope.getMaxY(); final double scaleX = (xmax - xmin) / npoints; final double scaleY = (ymax - ymin) / npoints; ReferencedEnvelope3D targetEnvelope = new ReferencedEnvelope3D(targetCRS); /* * Gets a first estimation using an algorithm capable to take singularity in account * (North pole, South pole, 180� longitude). We will expand this initial box later. */ CoordinateOperationFactory coordinateOperationFactory = CRS.getCoordinateOperationFactory(lenient); CoordinateOperation operation1 = coordinateOperationFactory.createOperation( sourceEnvelope.getCoordinateReferenceSystem(), DefaultGeographicCRS.WGS84); MathTransform transform1 = operation1.getMathTransform(); final CoordinateOperation operation2 = coordinateOperationFactory.createOperation( DefaultGeographicCRS.WGS84_3D, targetCRS); MathTransform transform2 = operation2.getMathTransform(); for (int t = 0; t < npoints; t++) { double dx = scaleX * t; double dy = scaleY * t; GeneralDirectPosition left = new GeneralDirectPosition(xmin, ymin + dy); DirectPosition pt = transformTo3D(left, transform1, transform2); targetEnvelope.expandToInclude(pt); GeneralDirectPosition top = new GeneralDirectPosition(xmin + dx, ymax); pt = transformTo3D(top, transform1, transform2); targetEnvelope.expandToInclude(pt); GeneralDirectPosition right = new GeneralDirectPosition(xmax, ymax - dy); pt = transformTo3D(right, transform1, transform2); targetEnvelope.expandToInclude(pt); GeneralDirectPosition bottom = new GeneralDirectPosition(xmax - dx, ymin); pt = transformTo3D(bottom, transform1, transform2); targetEnvelope.expandToInclude(pt); } return targetEnvelope; } /** * Transform from 3D down to 2D. * *

This method transforms each ordinate into WGS84, manually converts this to WGS84_3D with * the addition of a Double.NaN, and then transforms to the final 2D position. * * @return ReferencedEnvelope matching provided 2D TargetCRS */ public static ReferencedEnvelope transformTo2D( final ReferencedEnvelope sourceEnvelope, CoordinateReferenceSystem targetCRS, boolean lenient, int npoints) throws TransformException, OperationNotFoundException, FactoryException { final double xmin = sourceEnvelope.getMinX(); final double xmax = sourceEnvelope.getMaxX(); final double ymin = sourceEnvelope.getMinY(); final double ymax = sourceEnvelope.getMaxY(); final double scaleX = (xmax - xmin) / npoints; final double scaleY = (ymax - ymin) / npoints; final double zmin = sourceEnvelope.getMinimum(2); final double zmax = sourceEnvelope.getMaximum(2); final double scaleZ = (zmax - zmin) / npoints; // final double z = (zmax-zmin) / 2; // just average is fine as we are trying to remove // height ReferencedEnvelope targetEnvelope = new ReferencedEnvelope(targetCRS); /* * Gets a first estimation using an algorithm capable to take singularity in account * (North pole, South pole, 180� longitude). We will expand this initial box later. */ CoordinateOperationFactory coordinateOperationFactory = CRS.getCoordinateOperationFactory(lenient); CoordinateReferenceSystem sourceCRS = sourceEnvelope.getCoordinateReferenceSystem(); CoordinateOperation operation1 = coordinateOperationFactory.createOperation( sourceCRS, DefaultGeographicCRS.WGS84_3D); MathTransform transform1 = operation1.getMathTransform(); final CoordinateOperation operation2 = coordinateOperationFactory.createOperation(DefaultGeographicCRS.WGS84, targetCRS); MathTransform transform2 = operation2.getMathTransform(); for (int t = 0; t < npoints; t++) { double dx = scaleX * t; double dy = scaleY * t; for (int u = 0; u < npoints; u++) { double dz = scaleZ * u; double z = zmin + dz; GeneralDirectPosition left = new GeneralDirectPosition(xmin, ymin + dy, z); DirectPosition pt = transformTo2D(left, transform1, transform2); targetEnvelope.expandToInclude(pt); GeneralDirectPosition top = new GeneralDirectPosition(xmin + dx, ymax, z); pt = transformTo2D(top, transform1, transform2); targetEnvelope.expandToInclude(pt); GeneralDirectPosition right = new GeneralDirectPosition(xmax, ymax - dy, z); pt = transformTo2D(right, transform1, transform2); targetEnvelope.expandToInclude(pt); GeneralDirectPosition bottom = new GeneralDirectPosition(xmax - dx, ymax, z); pt = transformTo2D(bottom, transform1, transform2); targetEnvelope.expandToInclude(pt); if (zmin == zmax) { break; // only need one z sample } } } return targetEnvelope; } /** * Transform the provided 2D direct position into 3D (0 Ellipsoidal height assumed when * converting from {@link DefaultGeographicCRS#WGS84} to {@link DefaultGeographicCRS#WGS84_3D}). * * @param srcPosition Source 2D position * @param transformToWGS84 From source CRS to To WGS84 * @param transformFromWGS84_3D From WGS84_3D to target CRS * @return Position in target CRS as calculated by transform2 */ private static DirectPosition transformTo3D( GeneralDirectPosition srcPosition, MathTransform transformToWGS84, MathTransform transformFromWGS84_3D) throws TransformException { DirectPosition world2D = transformToWGS84.transform(srcPosition, null); DirectPosition world3D = new GeneralDirectPosition(DefaultGeographicCRS.WGS84_3D); world3D.setOrdinate(0, world2D.getOrdinate(0)); world3D.setOrdinate(1, world2D.getOrdinate(1)); world3D.setOrdinate(2, 0.0); // 0 elliposial height is assumed DirectPosition targetPosition = transformFromWGS84_3D.transform(world3D, null); return targetPosition; } /** * Transform the provided 3D direct position into 2D (Ellipsoidal height is ignored when * converting from {@link DefaultGeographicCRS#WGS84_3D} to {@link DefaultGeographicCRS#WGS84}). * * @param srcPosition Source 3D position * @param transformToWGS84_3D From source CRS to To WGS84_3D * @param transformFromWGS84 From WGS84 to target CRS * @return Position in target CRS as calculated by transform2 */ private static DirectPosition transformTo2D( GeneralDirectPosition srcPosition, MathTransform transformToWGS84_3D, MathTransform transformFromWGS84) throws TransformException { if (Double.isNaN(srcPosition.getOrdinate(2))) { srcPosition.setOrdinate( 2, 0.0); // lazy add 3rd ordinate if not provided to prevent failure } DirectPosition world3D = transformToWGS84_3D.transform(srcPosition, null); DirectPosition world2D = new GeneralDirectPosition(DefaultGeographicCRS.WGS84); world2D.setOrdinate(0, world3D.getOrdinate(0)); world2D.setOrdinate(1, world3D.getOrdinate(1)); DirectPosition targetPosition = transformFromWGS84.transform(world2D, null); return targetPosition; } /** * Transforms the geometry using the default transformer. * * @param geom The geom to transform * @param transform the transform to use during the transformation. * @return the transformed geometry. It will be a new geometry. * @throws MismatchedDimensionException if the geometry doesn't have the expected dimension for * the specified transform. * @throws TransformException if a point can't be transformed. */ public static Geometry transform(final Geometry geom, final MathTransform transform) throws MismatchedDimensionException, TransformException { final GeometryCoordinateSequenceTransformer transformer = new GeometryCoordinateSequenceTransformer(); transformer.setMathTransform(transform); return transformer.transform(geom); } /** * Transforms the coordinate using the provided math transform. * * @param source the source coordinate that will be transformed * @param dest the coordinate that will be set. May be null or the source coordinate (or new * coordinate of course). * @return the destination coordinate if not null or a new Coordinate. * @throws TransformException if the coordinate can't be transformed. */ public static Coordinate transform( final Coordinate source, Coordinate dest, final MathTransform transform) throws TransformException { ensureNonNull("source", source); ensureNonNull("transform", transform); if (dest == null) { dest = new Coordinate(); } final double[] array = new double[transform.getTargetDimensions()]; copy(source, array); transform.transform(array, 0, array, 0, 1); switch (transform.getTargetDimensions()) { case 3: dest.setZ(array[2]); // Fall through case 2: dest.y = array[1]; // Fall through case 1: dest.x = array[0]; // Fall through case 0: break; } return dest; } /** * Transforms the envelope from its current crs to {@link DefaultGeographicCRS#WGS84}. If the * specified envelope is already in WGS84, then it is returned unchanged. * *

The method {@link CRS#equalsIgnoreMetadata(Object, Object)} is used to compare the numeric * values and axis order (so {@code CRS.decode("CRS.84")} or {@code CRS.decode("4326",true)} * provide an appropriate match). * * @param envelope The envelope to transform. * @param crs The CRS the envelope is currently in. * @return The envelope transformed to be in {@link DefaultGeographicCRS#WGS84}. * @throws TransformException If at least one coordinate can't be transformed. */ public static Envelope toGeographic( final Envelope envelope, final CoordinateReferenceSystem crs) throws TransformException { if (CRS.equalsIgnoreMetadata(crs, DefaultGeographicCRS.WGS84)) { if (envelope instanceof ReferencedEnvelope) { return envelope; } return ReferencedEnvelope.create(envelope, DefaultGeographicCRS.WGS84); } ReferencedEnvelope initial = ReferencedEnvelope.create(envelope, crs); return toGeographic(initial); } /** * Transforms the envelope to {@link DefaultGeographicCRS#WGS84}. * *

This method will transform to {@link DefaultGeographicCRS#WGS84_3D} if necessary (and then * drop the height axis). * *

This method is identical to calling: envelope.transform(DefaultGeographicCRS.WGS84,true) * * @param envelope The envelope to transform * @return The envelope transformed to be in WGS84 CRS */ public static ReferencedEnvelope toGeographic(final ReferencedEnvelope envelope) throws TransformException { try { return envelope.transform(DefaultGeographicCRS.WGS84, true); } catch (FactoryException exception) { throw new TransformPathNotFoundException( Errors.format(ErrorKeys.CANT_TRANSFORM_ENVELOPE, exception)); } } /** * Like a transform but eXtreme! * *

Transforms an array of coordinates using the provided math transform. Each coordinate is * transformed separately. In case of a transform exception then the new value of the coordinate * is the last coordinate correctly transformed. * * @param transform The math transform to apply. * @param src The source coordinates. * @param dest The destination array for transformed coordinates. * @throws TransformException if this method failed to transform any of the points. */ public static void xform(final MathTransform transform, final double[] src, final double[] dest) throws TransformException { ensureNonNull("transform", transform); final int sourceDim = transform.getSourceDimensions(); final int targetDim = transform.getTargetDimensions(); if (targetDim != sourceDim) { throw new MismatchedDimensionException(); } TransformException firstError = null; boolean startPointTransformed = false; for (int i = 0; i < src.length; i += sourceDim) { try { transform.transform(src, i, dest, i, 1); if (!startPointTransformed) { startPointTransformed = true; for (int j = 0; j < i; j++) { System.arraycopy(dest, j, dest, i, targetDim); } } } catch (TransformException e) { if (firstError == null) { firstError = e; } if (startPointTransformed) { System.arraycopy(dest, i - targetDim, dest, i, targetDim); } } } if (!startPointTransformed && (firstError != null)) { throw firstError; } } /** * Computes the orthodromic distance between two points. This method: * *

* *

    *
  1. Transforms both points to geographic coordinates * (latitude,longitude). *
  2. Computes the orthodromic distance between the two points using ellipsoidal * calculations. *
* *

The real work is performed by {@link GeodeticCalculator}. This convenience method simply * manages a pool of pre-defined geodetic calculators for the given coordinate reference system * in order to avoid repetitive object creation. If a large amount of orthodromic distances need * to be computed, direct use of {@link GeodeticCalculator} provides better performance than * this convenience method. * * @param p1 First point * @param p2 Second point * @param crs Reference system the two points are in. * @return Orthodromic distance between the two points, in meters. * @throws TransformException if the coordinates can't be transformed from the specified CRS to * a {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}. */ public static synchronized double orthodromicDistance( final Coordinate p1, final Coordinate p2, final CoordinateReferenceSystem crs) throws TransformException { ensureNonNull("p1", p1); ensureNonNull("p2", p2); ensureNonNull("crs", crs); /* * Need to synchronize because we use a single instance of a Map (CALCULATORS) as well as * shared instances of GeodeticCalculator and GeneralDirectPosition (POSITIONS). None of * them are thread-safe. */ GeodeticCalculator gc = (GeodeticCalculator) CALCULATORS.get(crs); if (gc == null) { gc = new GeodeticCalculator(crs); CALCULATORS.put(crs, gc); } assert crs.equals(gc.getCoordinateReferenceSystem()) : crs; final GeneralDirectPosition pos = POSITIONS[Math.min(POSITIONS.length - 1, crs.getCoordinateSystem().getDimension())]; pos.setCoordinateReferenceSystem(crs); copy(p1, pos.ordinates); gc.setStartingPosition(pos); copy(p2, pos.ordinates); gc.setDestinationPosition(pos); return gc.getOrthodromicDistance(); } /** * Creates a DirectPosition from the provided point. * * @return DirectPosition */ public static DirectPosition toDirectPosition( final Coordinate point, final CoordinateReferenceSystem crs) { // GeneralDirectPosition directPosition = new GeneralDirectPosition(crs); // copy( point, directPosition.ordinates ); // return directPosition; return new AbstractDirectPosition() { public CoordinateReferenceSystem getCoordinateReferenceSystem() { return crs; } public int getDimension() { return crs.getCoordinateSystem().getDimension(); } public double getOrdinate(int dimension) throws IndexOutOfBoundsException { switch (dimension) { case 0: return point.x; case 1: return point.y; case 2: return point.getZ(); default: return Double.NaN; } } public void setOrdinate(int dimension, double value) throws IndexOutOfBoundsException { switch (dimension) { case 0: point.x = value; return; case 1: point.y = value; return; case 2: point.setZ(value); return; default: // ignore } } }; } /** * Copies the ordinates values from the specified JTS coordinates to the specified array. The * destination array can have any length. Only the relevant field of the source coordinate will * be copied. If the array length is greater than 3, then all extra dimensions will be set to * {@link Double#NaN NaN}. * * @param point The source coordinate. * @param ordinates The destination array. */ public static void copy(final Coordinate point, final double[] ordinates) { ensureNonNull("point", point); ensureNonNull("ordinates", ordinates); switch (ordinates.length) { default: Arrays.fill(ordinates, 3, ordinates.length, Double.NaN); // Fall through case 3: ordinates[2] = point.getZ(); // Fall through case 2: ordinates[1] = point.y; // Fall through case 1: ordinates[0] = point.x; // Fall through case 0: break; } } /** * Converts an arbitrary Java2D shape into a JTS geometry. The created JTS geometry may be any * of {@link LineString}, {@link LinearRing} or {@link MultiLineString}. * * @param shape the input shape * @return A new JTS geometry instance * @throws IllegalArgumentException if {@code shape} is {@code null} */ public static Geometry toGeometry(final Shape shape) { return toGeometry(shape, new GeometryFactory()); } /** * Converts an arbitrary Java2D shape into a JTS geometry. The created JTS geometry may be any * of {@link LineString}, {@link LinearRing} or {@link MultiLineString}. * * @param shape the input shape * @param factory the JTS factory to use to create the geometry * @return A new JTS geometry instance * @throws IllegalArgumentException if either {@code shape} or {@code factory} is {@code null} */ public static Geometry toGeometry(final Shape shape, final GeometryFactory factory) { ensureNonNull("shape", shape); ensureNonNull("factory", factory); final PathIterator iterator = shape.getPathIterator(null, ShapeUtilities.getFlatness(shape)); final double[] buffer = new double[6]; final List coords = new ArrayList(); final List lines = new ArrayList(); while (!iterator.isDone()) { switch (iterator.currentSegment(buffer)) { /* * Close the polygon: the last point is equal to the first point, and a LinearRing is * created. */ case PathIterator.SEG_CLOSE: { if (!coords.isEmpty()) { coords.add(coords.get(0)); lines.add( factory.createLinearRing( (Coordinate[]) coords.toArray(new Coordinate[coords.size()]))); coords.clear(); } break; } /* * General case: A LineString is created from previous points, and a new LineString * begin for next points. */ case PathIterator.SEG_MOVETO: { if (!coords.isEmpty()) { lines.add( factory.createLineString( (Coordinate[]) coords.toArray(new Coordinate[coords.size()]))); coords.clear(); } // Fall through } case PathIterator.SEG_LINETO: { coords.add(new Coordinate(buffer[0], buffer[1])); break; } default: throw new IllegalPathStateException(); } iterator.next(); } /* * End of loops: create the last LineString if any, then create the MultiLineString. */ if (!coords.isEmpty()) { lines.add( factory.createLineString( (Coordinate[]) coords.toArray(new Coordinate[coords.size()]))); } switch (lines.size()) { case 0: return null; case 1: return (LineString) lines.get(0); default: return factory.createMultiLineString(GeometryFactory.toLineStringArray(lines)); } } /** * Converts a JTS 2D envelope in an {@link Envelope2D} for interoperability with the referencing * package. * *

If the provided envelope is a {@link ReferencedEnvelope} we check that the provided CRS * and the implicit CRS are similar. * * @param envelope The JTS envelope to convert. * @param crs The coordinate reference system for the specified envelope. * @return The GeoAPI envelope. * @throws MismatchedDimensionException if a two-dimensional envelope can't be created from an * envelope with the specified CRS. * @since 2.3 */ public static Envelope2D getEnvelope2D( final Envelope envelope, final CoordinateReferenceSystem crs) throws MismatchedDimensionException { // Initial checks ensureNonNull("envelope", envelope); ensureNonNull("crs", crs); if (envelope instanceof ReferencedEnvelope) { final ReferencedEnvelope referenced = (ReferencedEnvelope) envelope; final CoordinateReferenceSystem implicitCRS = referenced.getCoordinateReferenceSystem(); if ((crs != null) && !CRS.equalsIgnoreMetadata(crs, implicitCRS)) { throw new IllegalArgumentException( Errors.format( ErrorKeys.MISMATCHED_ENVELOPE_CRS_$2, crs.getName().getCode(), implicitCRS.getName().getCode())); } } // Ensure the CRS is 2D and retrieve the new envelope final CoordinateReferenceSystem crs2D = CRS.getHorizontalCRS(crs); if (crs2D == null) throw new MismatchedDimensionException( Errors.format(ErrorKeys.CANT_SEPARATE_CRS_$1, crs)); return new Envelope2D( crs2D, envelope.getMinX(), envelope.getMinY(), envelope.getWidth(), envelope.getHeight()); } /** * Create a Point from a ISO Geometry DirectPosition. * * @return Point */ public static Point toGeometry(DirectPosition position) { return toGeometry(position, null); } /** * Create a Point from a ISO Geometry DirectPosition. * * @param factory Optional GeometryFactory * @return Point */ public static Point toGeometry(DirectPosition position, GeometryFactory factory) { if (factory == null) { factory = new GeometryFactory(); } Coordinate coordinate = new Coordinate(position.getOrdinate(0), position.getOrdinate(1)); if (position.getDimension() == 3) { coordinate.setZ(position.getOrdinate(2)); } return factory.createPoint(coordinate); } /** * Converts an envelope to a JTS polygon. * *

The resulting polygon contains an outer ring with vertices: * (x1,y1),(x2,y1),(x2,y2),(x1,y2),(x1,y1) * * @param envelope The original envelope. * @return The envelope as a polygon. * @throws IllegalArgumentException if {@code env} is {@code null} * @since 2.4 */ public static Polygon toGeometry(Envelope envelope) { return toGeometry(envelope, new GeometryFactory()); } /** * Converts an envelope to a JTS polygon using the given JTS geometry factory. * *

The resulting polygon contains an outer ring with vertices: * (x1,y1),(x2,y1),(x2,y2),(x1,y2),(x1,y1) * * @param envelope The original envelope. * @return The envelope as a polygon. * @since 2.8 * @throws IllegalArgumentException if either {@code env} or {@code factory} is {@code null} */ public static Polygon toGeometry(final Envelope envelope, GeometryFactory factory) { ensureNonNull("env", envelope); if (factory == null) { factory = new GeometryFactory(); } Polygon polygon = factory.createPolygon( factory.createLinearRing( new Coordinate[] { new Coordinate(envelope.getMinX(), envelope.getMinY()), new Coordinate(envelope.getMaxX(), envelope.getMinY()), new Coordinate(envelope.getMaxX(), envelope.getMaxY()), new Coordinate(envelope.getMinX(), envelope.getMaxY()), new Coordinate(envelope.getMinX(), envelope.getMinY()) }), null); if (envelope instanceof ReferencedEnvelope) { ReferencedEnvelope refEnv = (ReferencedEnvelope) envelope; polygon.setUserData(refEnv.getCoordinateReferenceSystem()); } return polygon; } /** * Create a ReferencedEnvelope from the provided geometry, we will do our best to guess the * CoordinateReferenceSystem making use of getUserData() and getSRID() as needed. * * @param geom Provided Geometry * @return ReferencedEnvelope describing the bounds of the provided Geometry */ public static ReferencedEnvelope toEnvelope(Geometry geom) { if (geom == null) { return null; // return new ReferencedEnvelope(); // very empty! } String srsName = null; Object userData = geom.getUserData(); if (userData != null && userData instanceof String) { srsName = (String) userData; } else if (geom.getSRID() > 0) { srsName = "EPSG:" + geom.getSRID(); } CoordinateReferenceSystem crs = null; if (userData != null && userData instanceof CoordinateReferenceSystem) { crs = (CoordinateReferenceSystem) userData; } else if (srsName != null) { try { crs = CRS.decode(srsName); } catch (NoSuchAuthorityCodeException e) { // e.printStackTrace(); } catch (FactoryException e) { // e.printStackTrace(); } } return new ReferencedEnvelope(geom.getEnvelopeInternal(), crs); } /** * Converts a {@link ReferencedEnvelope} to a JTS polygon. * *

The resulting polygon contains an outer ring with vertices: * (x1,y1),(x2,y1),(x2,y2),(x1,y2),(x1,y1) * * @param bbox The original envelope. * @return The envelope as a polygon. * @throws IllegalArgumentException if {@code bbox} is {@code null} * @since 2.4 */ public static Polygon toGeometry(ReferencedEnvelope bbox) { return toGeometry((BoundingBox) bbox, new GeometryFactory()); } /** * Convert the provided bbox to a polygon, sampling a set number of points along each side. * * @param bbox bounding box being converted to a Polygon * @param factory geometry factory used to create the polygon * @param npoints number of points to sample along each edge * @return Polygon */ public static Polygon toGeometry(BoundingBox bbox, GeometryFactory factory, int npoints) { npoints++; // for the starting point. if (bbox == null) { return null; } if (factory == null) { factory = new GeometryFactory(); } final Coordinate[] coordinates = new Coordinate[(4 * npoints)]; final double xmin = bbox.getMinX(); final double xmax = bbox.getMaxX(); final double ymin = bbox.getMinY(); final double ymax = bbox.getMaxY(); final double scaleX = (xmax - xmin) / npoints; final double scaleY = (ymax - ymin) / npoints; int top = 0; int right = npoints; int bottom = npoints * 2; int left = npoints * 3; for (int t = 0; t < npoints; t++) { final double dx = scaleX * t; final double dy = scaleY * t; coordinates[top + t] = new Coordinate(xmin + dx, ymax); coordinates[left + t] = new Coordinate(xmin, ymin + dy); coordinates[bottom + t] = new Coordinate(xmax - dx, ymin); coordinates[right + t] = new Coordinate(xmax, ymax - dy); } Polygon polygon = factory.createPolygon(factory.createLinearRing(coordinates), null); polygon.setUserData(bbox.getCoordinateReferenceSystem()); return polygon; } /** * Transforms the geometry from its current crs to {@link DefaultGeographicCRS#WGS84}. If the * specified geometry is already in WGS84, then it is returned unchanged. * *

The method {@link CRS#equalsIgnoreMetadata(Object, Object)} is used to compare the numeric * values and axis order (so {@code CRS.decode("CRS.84")} or {@code CRS.decode("4326",true)} * provide an appropriate match). * * @param geom The geometry to transform. * @param crs The CRS the geometry is currently in. * @return The geometry transformed to be in {@link DefaultGeographicCRS#WGS84}. * @throws TransformException If at least one coordinate can't be transformed. */ public static Geometry toGeographic(Geometry geom, final CoordinateReferenceSystem crs) throws TransformException { if (crs == null) { return geom; } if (crs.getCoordinateSystem().getDimension() >= 3) { try { MathTransform transform = CRS.findMathTransform(crs, DefaultGeographicCRS.WGS84_3D); Geometry geometry = transform(geom, transform); return geometry; // The extra Z values will be ignored } catch (FactoryException exception) { throw new TransformException(Errors.format(ErrorKeys.CANT_REPROJECT_$1, crs)); } } else if (CRS.equalsIgnoreMetadata(crs, DefaultGeographicCRS.WGS84)) { return geom; } else { try { MathTransform transform = CRS.findMathTransform(crs, DefaultGeographicCRS.WGS84); return transform(geom, transform); } catch (FactoryException exception) { throw new TransformException(Errors.format(ErrorKeys.CANT_REPROJECT_$1, crs)); } } } /** * Converts a {@link BoundingBox} to a JTS polygon. * *

The resulting polygon contains an outer ring with vertices: * (x1,y1),(x2,y1),(x2,y2),(x1,y2),(x1,y1) * * @param bbox The original envelope. * @return The envelope as a polygon. * @throws IllegalArgumentException if {@code bbox} is {@code null} * @since 2.4 */ public static Polygon toGeometry(BoundingBox bbox) { return toGeometry(bbox, new GeometryFactory()); } /** * Converts a {@link BoundingBox} to a JTS polygon using the given JTS geometry factory. * *

The resulting polygon contains an outer ring with vertices: * (x1,y1),(x2,y1),(x2,y2),(x1,y2),(x1,y1) * * @param bbox The original envelope. * @return The envelope as a polygon. * @since 2.8 * @throws IllegalArgumentException if either {@code bbox} or {@code factory} is {@code null} */ public static Polygon toGeometry(BoundingBox bbox, final GeometryFactory factory) { ensureNonNull("bbox", bbox); ensureNonNull("factory", factory); Polygon polygon = factory.createPolygon( factory.createLinearRing( new Coordinate[] { new Coordinate(bbox.getMinX(), bbox.getMinY()), new Coordinate(bbox.getMaxX(), bbox.getMinY()), new Coordinate(bbox.getMaxX(), bbox.getMaxY()), new Coordinate(bbox.getMinX(), bbox.getMaxY()), new Coordinate(bbox.getMinX(), bbox.getMinY()) }), null); polygon.setUserData(bbox.getCoordinateReferenceSystem()); return polygon; } /** * Checks a Geometry coordinates are within the area of validity of the specified reference * system. If a coordinate falls outside the area of validity a {@link * PointOutsideEnvelopeException} is thrown * * @param geom the geometry to check * @param crs the crs that defines the are of validity (must not be null) * @since 2.4 */ public static void checkCoordinatesRange(Geometry geom, CoordinateReferenceSystem crs) throws PointOutsideEnvelopeException { // named x,y, but could be anything CoordinateSystemAxis x = crs.getCoordinateSystem().getAxis(0); CoordinateSystemAxis y = crs.getCoordinateSystem().getAxis(1); // check if unbounded, many projected systems are, in this case no check // is needed boolean xUnbounded = Double.isInfinite(x.getMinimumValue()) && Double.isInfinite(x.getMaximumValue()); boolean yUnbounded = Double.isInfinite(y.getMinimumValue()) && Double.isInfinite(y.getMaximumValue()); if (xUnbounded && yUnbounded) { return; } // check each coordinate Coordinate[] c = geom.getCoordinates(); for (int i = 0; i < c.length; i++) { if (!xUnbounded && ((c[i].x < x.getMinimumValue()) || (c[i].x > x.getMaximumValue()))) { throw new PointOutsideEnvelopeException( c[i].x + " outside of (" + x.getMinimumValue() + "," + x.getMaximumValue() + ")"); } if (!yUnbounded && ((c[i].y < y.getMinimumValue()) || (c[i].y > y.getMaximumValue()))) { throw new PointOutsideEnvelopeException( c[i].y + " outside of (" + y.getMinimumValue() + "," + y.getMaximumValue() + ")"); } } } /** * Creates a smoothed copy of the input Geometry. This is only useful for polygonal and lineal * geometries. Point objects will be returned unchanged. The smoothing algorithm inserts new * vertices which are positioned using Bezier splines. All vertices of the input Geometry will * be present in the output Geometry. * *

The {@code fit} parameter controls how tightly the smoothed lines conform to the input * line segments, with a value of 1 being tightest and 0 being loosest. Values outside this * range will be adjusted up or down as required. * *

The input Geometry can be a simple type (e.g. LineString, Polygon), a multi-type (e.g. * MultiLineString, MultiPolygon) or a GeometryCollection. The returned object will be of the * same type. * * @param geom the input geometry * @param fit tightness of fit from 0 (loose) to 1 (tight) * @return a new Geometry object of the same class as {@code geom} * @throws IllegalArgumentException if {@code geom} is {@code null} */ public static Geometry smooth(final Geometry geom, double fit) { return smooth(geom, fit, new GeometryFactory()); } /** * Creates a smoothed copy of the input Geometry. This is only useful for polygonal and lineal * geometries. Point objects will be returned unchanged. The smoothing algorithm inserts new * vertices which are positioned using Bezier splines. All vertices of the input Geometry will * be present in the output Geometry. * *

The {@code fit} parameter controls how tightly the smoothed lines conform to the input * line segments, with a value of 1 being tightest and 0 being loosest. Values outside this * range will be adjusted up or down as required. * *

The input Geometry can be a simple type (e.g. LineString, Polygon), a multi-type (e.g. * MultiLineString, MultiPolygon) or a GeometryCollection. The returned object will be of the * same type. * * @param geom the input geometry * @param fit tightness of fit from 0 (loose) to 1 (tight) * @param factory the GeometryFactory to use for creating smoothed objects * @return a new Geometry object of the same class as {@code geom} * @throws IllegalArgumentException if either {@code geom} or {@code factory} is {@code null} */ public static Geometry smooth(final Geometry geom, double fit, final GeometryFactory factory) { ensureNonNull("geom", geom); ensureNonNull("factory", factory); // Adjust fit if necessary fit = Math.max(0.0, Math.min(1.0, fit)); return smooth(geom, fit, factory, new GeometrySmoother(factory)); } private static Geometry smooth( final Geometry geom, final double fit, final GeometryFactory factory, GeometrySmoother smoother) { switch (Geometries.get(geom)) { case POINT: case MULTIPOINT: // For points, just return the input geometry return geom; case LINESTRING: // This handles open and closed lines (LinearRings) return smoothLineString(factory, smoother, geom, fit); case MULTILINESTRING: return smoothMultiLineString(factory, smoother, geom, fit); case POLYGON: return smoother.smooth((Polygon) geom, fit); case MULTIPOLYGON: return smoothMultiPolygon(factory, smoother, geom, fit); case GEOMETRYCOLLECTION: return smoothGeometryCollection(factory, smoother, geom, fit); default: throw new UnsupportedOperationException( "No smoothing method available for " + geom.getGeometryType()); } } private static Geometry smoothLineString( GeometryFactory factory, GeometrySmoother smoother, Geometry geom, double fit) { if (geom instanceof LinearRing) { // Treat as a Polygon Polygon poly = factory.createPolygon((LinearRing) geom, null); Polygon smoothed = smoother.smooth(poly, fit); return smoothed.getExteriorRing(); } else { return smoother.smooth((LineString) geom, fit); } } private static Geometry smoothMultiLineString( GeometryFactory factory, GeometrySmoother smoother, Geometry geom, double fit) { final int N = geom.getNumGeometries(); LineString[] smoothed = new LineString[N]; for (int i = 0; i < N; i++) { smoothed[i] = (LineString) smoothLineString(factory, smoother, geom.getGeometryN(i), fit); } return factory.createMultiLineString(smoothed); } private static Geometry smoothMultiPolygon( GeometryFactory factory, GeometrySmoother smoother, Geometry geom, double fit) { final int N = geom.getNumGeometries(); Polygon[] smoothed = new Polygon[N]; for (int i = 0; i < N; i++) { smoothed[i] = smoother.smooth((Polygon) geom.getGeometryN(i), fit); } return factory.createMultiPolygon(smoothed); } private static Geometry smoothGeometryCollection( GeometryFactory factory, GeometrySmoother smoother, Geometry geom, double fit) { final int N = geom.getNumGeometries(); Geometry[] smoothed = new Geometry[N]; for (int i = 0; i < N; i++) { smoothed[i] = smooth(geom.getGeometryN(i), fit, factory, smoother); } return factory.createGeometryCollection(smoothed); } /** * Creates a {@link CoordinateSequence} using the provided factory confirming the provided size * and dimension are respected. * *

If the requested dimension is larger than the CoordinateSequence implementation can * provide, then a sequence of maximum possible dimension should be created. An error should not * be thrown. * *

This method is functionally identical to calling csFactory.create(size,dim) - it contains * additional logic to work around a limitation on the commonly used * CoordinateArraySequenceFactory. * * @param size the number of coordinates in the sequence * @param dimension the dimension of the coordinates in the sequence */ public static CoordinateSequence createCS( CoordinateSequenceFactory csFactory, int size, int dimension) { // the coordinates don't have measures return createCS(csFactory, size, dimension, 0); } /** * Creates a {@link CoordinateSequence} using the provided factory confirming the provided size * and dimension are respected. * *

If the requested dimension is larger than the CoordinateSequence implementation can * provide, then a sequence of maximum possible dimension should be created. An error should not * be thrown. * *

This method is functionally identical to calling csFactory.create(size,dim) - it contains * additional logic to work around a limitation on the commonly used * CoordinateArraySequenceFactory. * * @param size the number of coordinates in the sequence * @param dimension the dimension of the coordinates in the sequence * @param measures the measures of the coordinates in the sequence */ public static CoordinateSequence createCS( CoordinateSequenceFactory csFactory, int size, int dimension, int measures) { CoordinateSequence cs; if (csFactory instanceof CoordinateArraySequenceFactory && dimension == 1) { // work around JTS 1.14 CoordinateArraySequenceFactory regression ignoring provided // dimension cs = new CoordinateArraySequence(size, dimension, measures); } else { cs = csFactory.create(size, dimension, measures); } if (cs.getDimension() != dimension) { // illegal state error, try and fix throw new IllegalStateException( "Unable to use" + csFactory + " to produce CoordinateSequence with dimension " + dimension); } return cs; } /** * Replacement for geometry.getEnvelopeInternal() that returns ReferencedEnvelope or * ReferencedEnvelope3D as appropriate for the provided CRS. * * @return ReferencedEnvelope (or ReferencedEnvelope3D) as appropriate */ public static ReferencedEnvelope bounds(Geometry geometry, CoordinateReferenceSystem crs) { if (geometry == null) { return null; } if (crs == null) { return new ReferencedEnvelope(geometry.getEnvelopeInternal(), null); // CRS is not known } else if (crs.getCoordinateSystem().getDimension() >= 3) { ReferencedEnvelope bounds = new ReferencedEnvelope3D(crs); // Note we are visiting all coordinates (rather than just the outer rings // polygons) as holes may contribute to the min / max bounds. for (Coordinate coordinate : geometry.getCoordinates()) { bounds.expandToInclude(coordinate); } return bounds; } else { return new ReferencedEnvelope(geometry.getEnvelopeInternal(), crs); } } /** * Removes collinear points from the provided linestring. * * @param ls the {@link LineString} to be simplified. * @return a new version of the provided {@link LineString} with collinear points removed. */ static LineString removeCollinearVertices(final LineString ls) { if (ls == null) { throw new NullPointerException("The provided linestring is null"); } final int N = ls.getNumPoints(); final boolean isLinearRing = ls instanceof LinearRing; List retain = new ArrayList(); retain.add(ls.getCoordinateN(0)); int i0 = 0, i1 = 1, i2 = 2; Coordinate firstCoord = ls.getCoordinateN(i0); Coordinate midCoord; Coordinate lastCoord; while (i2 < N) { midCoord = ls.getCoordinateN(i1); lastCoord = ls.getCoordinateN(i2); final int orientation = Orientation.index(firstCoord, midCoord, lastCoord); // Colllinearity test if (orientation != Orientation.COLLINEAR) { // add midcoord and change head retain.add(midCoord); i0 = i1; firstCoord = ls.getCoordinateN(i0); } i1++; i2++; } retain.add(ls.getCoordinateN(N - 1)); // // Return value // final int size = retain.size(); // nothing changed? if (size == N) { // free everything and return original retain.clear(); return ls; } return isLinearRing ? ls.getFactory().createLinearRing(retain.toArray(new Coordinate[size])) : ls.getFactory().createLineString(retain.toArray(new Coordinate[size])); } /** * Removes collinear vertices from the provided {@link Polygon}. * * @param polygon the instance of a {@link Polygon} to remove collinear vertices from. * @return a new instance of the provided {@link Polygon} without collinear vertices. */ static Polygon removeCollinearVertices(final Polygon polygon) { if (polygon == null) { throw new NullPointerException("The provided Polygon is null"); } // reuse existing factory final GeometryFactory gf = polygon.getFactory(); // work on the exterior ring LineString exterior = polygon.getExteriorRing(); LineString shell = removeCollinearVertices(exterior); if ((shell == null) || shell.isEmpty()) { return null; } // work on the holes List holes = new ArrayList(); final int size = polygon.getNumInteriorRing(); for (int i = 0; i < size; i++) { LineString hole = polygon.getInteriorRingN(i); hole = removeCollinearVertices(hole); if ((hole != null) && !hole.isEmpty()) { holes.add(hole); } } return gf.createPolygon((LinearRing) shell, holes.toArray(new LinearRing[holes.size()])); } /** * Removes collinear vertices from the provided {@link Geometry}. * *

For the moment this implementation only accepts, {@link Polygon}, {@link LineString} and * {@link MultiPolygon} It will throw an exception if the geometry is not one of those types * * @param g the instance of a {@link Geometry} to remove collinear vertices from. * @return a new instance of the provided {@link Geometry} without collinear vertices. */ public static Geometry removeCollinearVertices(final Geometry g) { if (g == null) { throw new NullPointerException("The provided Geometry is null"); } if (g instanceof LineString) { return removeCollinearVertices((LineString) g); } else if (g instanceof Polygon) { return removeCollinearVertices((Polygon) g); } else if (g instanceof MultiPolygon) { MultiPolygon mp = (MultiPolygon) g; Polygon[] parts = new Polygon[mp.getNumGeometries()]; for (int i = 0; i < mp.getNumGeometries(); i++) { Polygon part = (Polygon) mp.getGeometryN(i); part = removeCollinearVertices(part); parts[i] = part; } return g.getFactory().createMultiPolygon(parts); } throw new IllegalArgumentException( "This method can work on LineString, Polygon and Multipolygon: " + g.getClass()); } /** * Removes collinear vertices from the provided {@link Geometry} if the number of point exceeds * the requested minPoints. * *

For the moment this implementation only accepts, {@link Polygon}, {@link LineString} and * {@link MultiPolygon} It will throw an exception if the geometry is not one of those types * * @param geometry the instance of a {@link Geometry} to remove collinear vertices from. * @param minPoints perform removal of collinear points if num of vertices exceeds minPoints. * @return a new instance of the provided {@link Geometry} without collinear vertices. */ public static Geometry removeCollinearVertices(final Geometry geometry, int minPoints) { ensureNonNull("geometry", geometry); if ((minPoints <= 0) || (geometry.getNumPoints() < minPoints)) { return geometry; } if (geometry instanceof LineString) { return removeCollinearVertices((LineString) geometry); } else if (geometry instanceof Polygon) { return removeCollinearVertices((Polygon) geometry); } else if (geometry instanceof MultiPolygon) { MultiPolygon mp = (MultiPolygon) geometry; Polygon[] parts = new Polygon[mp.getNumGeometries()]; for (int i = 0; i < mp.getNumGeometries(); i++) { Polygon part = (Polygon) mp.getGeometryN(i); part = removeCollinearVertices(part); parts[i] = part; } return geometry.getFactory().createMultiPolygon(parts); } throw new IllegalArgumentException( "This method can work on LineString, Polygon and Multipolygon: " + geometry.getClass()); } /** * Given a potentially invalid polygon it rebuilds it as a list of valid polygons, eventually * removing the holes */ public static List makeValid(Polygon polygon, boolean removeHoles) { // add all segments into the polygonizer final Polygonizer p = new Polygonizer(); polygon.apply( new CoordinateSequenceFilter() { public boolean isGeometryChanged() { return false; } public boolean isDone() { return false; } public void filter(CoordinateSequence seq, int i) { if (i == 0) { return; } p.add( new GeometryFactory() .createLineString( new Coordinate[] { seq.getCoordinate(i - 1), seq.getCoordinate(i) })); } }); List result = new ArrayList(p.getPolygons()); // if necessary throw away the holes and return just the shells if (removeHoles) { for (int i = 0; i < result.size(); i++) { Polygon item = result.get(i); if (item.getNumInteriorRing() > 0) { GeometryFactory factory = item.getFactory(); Polygon noHoles = factory.createPolygon((LinearRing) item.getExteriorRing(), null); result.set(i, noHoles); } } } return result; } /** * Converts a JTS Envelope into an equivalent {@link Rectangle2D} * * @param envelope The source envelope */ public static Rectangle2D toRectangle2D(Envelope envelope) { if (envelope == null) { return null; } return new Rectangle2D.Double( envelope.getMinX(), envelope.getMinY(), envelope.getWidth(), envelope.getHeight()); } /** * Converts a AWT {@link Rectangle2D} into a JTS Envelope * * @param rectangle The source rectangle */ public static Envelope toEnvelope(Rectangle2D rectangle) { if (rectangle == null) { return null; } return new Envelope( rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY()); } /** * Converts a AWT polygon into a JTS one (unlike {@link toGeometry} which always returns lines * instead) */ public static Polygon toPolygon(java.awt.Polygon polygon) { return toPolygonInternal(polygon); } /** * Converts a AWT rectangle into a JTS one (unlike {@link toGeometry} which always returns lines * instead) */ public static Polygon toPolygon(java.awt.Rectangle rectangle) { return toPolygonInternal(rectangle); } /** * Converts a AWT rectangle into a JTS one (unlike {@link toGeometry} which always returns lines * instead) */ public static Polygon toPolygon(Rectangle2D rectangle) { return toPolygonInternal(rectangle); } private static Polygon toPolygonInternal(Shape shape) { Geometry geomROI = null; if (shape != null) { geomROI = ShapeReader.read(shape, 0, new GeometryFactory()); geomROI.apply(Y_INVERSION); } return (Polygon) geomROI; } /** * Envelope equality with target tolerance. * * @param e1 The first envelope * @param e2 The second envelope * @param tolerance The tolerance * @return True if the envelopes have the same boundaries, minus the given tolerance */ public static boolean equals(Envelope e1, Envelope e2, double tolerance) { return Math.abs(e1.getMinX() - e2.getMinX()) < tolerance && Math.abs(e1.getMinY() - e2.getMinY()) < tolerance && Math.abs(e1.getMaxX() - e2.getMaxX()) < tolerance && Math.abs(e1.getMaxY() - e2.getMaxY()) < tolerance; } /** * BoundingBox equality with target tolerance. This method compares also coordinate reference * systems. * * @param a The first envelope * @param b The second envelope * @param tolerance The tolerance * @return True if the envelopes have the same boundaries, minus the given tolerance, and the * CRSs are equal according to CRS#equalsIgnoreMetadata */ public static boolean equals(BoundingBox a, BoundingBox b, double tolerance) { boolean flatEqual = Math.abs(a.getMinX() - b.getMinX()) <= tolerance && Math.abs(a.getMinY() - b.getMinY()) <= tolerance && Math.abs(a.getMaxX() - b.getMaxX()) <= tolerance && Math.abs(a.getMaxY() - b.getMaxY()) <= tolerance && CRS.equalsIgnoreMetadata( a.getCoordinateReferenceSystem(), b.getCoordinateReferenceSystem()); if (!flatEqual) return false; if (a instanceof BoundingBox3D && b instanceof BoundingBox3D) { BoundingBox3D a3 = (BoundingBox3D) a; BoundingBox3D b3 = (BoundingBox3D) b; return Math.abs(a3.getMinZ() - b3.getMinZ()) <= tolerance && Math.abs(a3.getMaxZ() - b3.getMaxZ()) <= tolerance; } else if (a instanceof BoundingBox3D && !(b instanceof BoundingBox3D) || !(a instanceof BoundingBox3D) && b instanceof BoundingBox3D) { return false; } return true; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy