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

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

Go to download

The main module contains the GeoTools public interfaces that are used by other GeoTools modules (and GeoTools applications). Where possible we make use industry standard terms as provided by OGC and ISO standards. The formal GeoTools public api consists of gt-metadata, jts and the gt-main module. The main module contains the default implementations that are available provided to other GeoTools modules using our factory system. Factories are obtained from an appropriate FactoryFinder, giving applications a chance configure the factory used using the Factory Hints facilities. FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Expression expr = ff.add( expression1, expression2 ); If you find yourself using implementation specific classes chances are you doing it wrong: Expression expr = new AddImpl( expression1, expressiom2 );

There is a newer version: 24.2-oss84-1
Show newest version
/*
 *    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