/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.model; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.revolsys.collection.CollectionUtil; import com.revolsys.collection.map.IntHashMap; import com.revolsys.collection.map.LinkedHashMapEx; import com.revolsys.collection.map.MapEx; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.cs.CoordinateSystem; import com.revolsys.geometry.cs.GeographicCoordinateSystem; import com.revolsys.geometry.cs.ProjectedCoordinateSystem; import com.revolsys.geometry.cs.epsg.EpsgCoordinateSystems; import com.revolsys.geometry.cs.esri.EsriCoordinateSystems; import com.revolsys.geometry.cs.projection.CoordinatesOperation; import com.revolsys.geometry.cs.projection.ProjectionFactory; import com.revolsys.geometry.graph.linemerge.LineMerger; import com.revolsys.geometry.model.impl.AbstractPoint; import com.revolsys.geometry.model.impl.BoundingBoxDoubleGf; import com.revolsys.geometry.model.impl.BoundingBoxDoubleXYGeometryFactory; import com.revolsys.geometry.model.impl.BoundingBoxGeometryFactory; import com.revolsys.geometry.model.impl.GeometryCollectionImpl; import com.revolsys.geometry.model.impl.LineStringDoubleBuilder; import com.revolsys.geometry.model.impl.LineStringDoubleGf; import com.revolsys.geometry.model.impl.LinearRingDoubleGf; import com.revolsys.geometry.model.impl.MultiLineStringImpl; import com.revolsys.geometry.model.impl.MultiPointImpl; import com.revolsys.geometry.model.impl.MultiPolygonImpl; import com.revolsys.geometry.model.impl.PointDouble; import com.revolsys.geometry.model.impl.PointDoubleGf; import com.revolsys.geometry.model.impl.PointDoubleXYGeometryFactory; import com.revolsys.geometry.model.impl.PolygonImpl; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.segment.LineSegmentDoubleGF; import com.revolsys.geometry.util.BoundingBoxUtil; import com.revolsys.io.map.MapSerializer; import com.revolsys.record.io.format.wkt.WktParser; import com.revolsys.util.MathUtil; import com.revolsys.util.Property; /** * Supplies a set of utility methods for building Geometry objects from lists * of Coordinates. * <p> * Note that the factory constructor methods do <b>not</b> change the input coordinates in any way. * In particular, they are not rounded to the supplied <tt>PrecisionModel</tt>. * It is assumed that input Point meet the given precision. * * * @version 1.7 */ public class GeometryFactory implements GeometryFactoryProxy, Serializable, MapSerializer { private class EmptyPoint extends AbstractPoint { private static final long serialVersionUID = 1L; @Override public Point clone() { return this; } @Override public void copyCoordinates(final double[] coordinates) { Arrays.fill(coordinates, java.lang.Double.NaN); } @Override public int getAxisCount() { return GeometryFactory.this.axisCount; } @Override public double getCoordinate(final int axisIndex) { return java.lang.Double.NaN; } @Override public GeometryFactory getGeometryFactory() { return GeometryFactory.this; } @Override public double getM() { return java.lang.Double.NaN; } @Override public double getX() { return java.lang.Double.NaN; } @Override public double getY() { return java.lang.Double.NaN; } @Override public double getZ() { return java.lang.Double.NaN; } @Override public boolean isEmpty() { return true; } @Override public String toString() { return toEwkt(); } } private static final IntHashMap<IntHashMap<List<GeometryFactory>>> factoriesBySrid = new IntHashMap<>(); private static final double[] SCALES_FLOATING_2 = new double[2]; public static final double[] SCALES_FLOATING_3 = new double[3]; public static final GeometryFactory DEFAULT_2D = fixed(0, SCALES_FLOATING_2); /** * The default GeometryFactory with no coordinate system, 3D axis (x, y & z) and a floating precision model. */ public static final GeometryFactory DEFAULT_3D = fixed(0, SCALES_FLOATING_3); private static final long serialVersionUID = 4328651897279304108L; public static final BoundingBox boundingBox(final Geometry geometry) { if (geometry == null) { return DEFAULT_3D.newBoundingBoxEmpty(); } else { return geometry.getBoundingBox(); } } public static void clear() { factoriesBySrid.clear(); } public static GeometryFactory fixed(final CoordinateSystem coordinateSystem, final int axisCount, final double... scales) { if (coordinateSystem == null) { return fixed(0, axisCount, scales); } else { final int coordinateSystemId = coordinateSystem.getCoordinateSystemId(); if (coordinateSystemId == 0) { return new GeometryFactory(coordinateSystem, axisCount, scales); } else { return fixed(coordinateSystem, coordinateSystemId, axisCount, scales); } } } /** * <p> * Get a GeometryFactory with the coordinate system, number of axis and a * fixed x, y & fixed z precision models. * </p> * * @param coordinateSystemId The <a href="http://spatialreference.org/ref/epsg/">EPSG * coordinate system id</a>. * @param axisCount The number of coordinate axis. 2 for 2D x & y * coordinates. 3 for 3D x, y & z coordinates. * @param scaleXY The scale factor used to round the x, y coordinates. The * precision is 1 / scaleXy. A scale factor of 1000 will give a * precision of 1 / 1000 = 1mm for projected coordinate systems using * metres. * @param scaleZ The scale factor used to round the z coordinates. The * precision is 1 / scaleZ. A scale factor of 1000 will give a * precision of 1 / 1000 = 1mm for projected coordinate systems using * metres. * @return The geometry factory. */ private static GeometryFactory fixed(final CoordinateSystem coordinateSystem, final int coordinateSystemId, final int axisCount, final double... scales) { synchronized (factoriesBySrid) { GeometryFactory factory = null; IntHashMap<List<GeometryFactory>> factoriesByAxisCount = factoriesBySrid .get(coordinateSystemId); if (factoriesByAxisCount == null) { factoriesByAxisCount = new IntHashMap<>(); factoriesBySrid.put(coordinateSystemId, factoriesByAxisCount); } List<GeometryFactory> factories = factoriesByAxisCount.get(axisCount); if (factories == null) { factories = new ArrayList<>(); factoriesByAxisCount.put(axisCount, factories); } else { final int size = factories.size(); for (int i = 0; i < size; i++) { final GeometryFactory matchFactory = factories.get(i); if (matchFactory.equalsScales(scales)) { return matchFactory; } } } if (factory == null) { factory = new GeometryFactory(coordinateSystem, coordinateSystemId, axisCount, scales); factories.add(factory); } return factory; } } /** * <p> * Get a GeometryFactory with the coordinate system, 2D axis (x & y) and a * fixed x, y precision model. * </p> * * @param coordinateSystemId The <a href="http://spatialreference.org/ref/epsg/">EPSG * coordinate system id</a>. * @param scaleXY The scale factor used to round the x, y coordinates. The * precision is 1 / scaleXy. A scale factor of 1000 will give a * precision of 1 / 1000 = 1mm for projected coordinate systems using * metres. * @return The geometry factory. */ public static GeometryFactory fixed(final int coordinateSystemId, final double... scales) { return fixed(coordinateSystemId, scales.length, scales); } /** * <p> * Get a GeometryFactory with the coordinate system, number of axis and a * fixed x, y & fixed z precision models. * </p> * * @param coordinateSystemId The <a href="http://spatialreference.org/ref/epsg/">EPSG * coordinate system id</a>. * @param axisCount The number of coordinate axis. 2 for 2D x & y * coordinates. 3 for 3D x, y & z coordinates. * @param scaleXY The scale factor used to round the x, y coordinates. The * precision is 1 / scaleXy. A scale factor of 1000 will give a * precision of 1 / 1000 = 1mm for projected coordinate systems using * metres. * @param scaleZ The scale factor used to round the z coordinates. The * precision is 1 / scaleZ. A scale factor of 1000 will give a * precision of 1 / 1000 = 1mm for projected coordinate systems using * metres. * @return The geometry factory. */ public static GeometryFactory fixed(final int coordinateSystemId, final int axisCount, final double... scales) { synchronized (factoriesBySrid) { GeometryFactory factory = null; IntHashMap<List<GeometryFactory>> factoriesByAxisCount = factoriesBySrid .get(coordinateSystemId); if (factoriesByAxisCount == null) { factoriesByAxisCount = new IntHashMap<>(); factoriesBySrid.put(coordinateSystemId, factoriesByAxisCount); } List<GeometryFactory> factories = factoriesByAxisCount.get(axisCount); if (factories == null) { factories = new ArrayList<>(); factoriesByAxisCount.put(axisCount, factories); } else { final int size = factories.size(); for (int i = 0; i < size; i++) { final GeometryFactory matchFactory = factories.get(i); if (matchFactory.equalsScales(scales)) { return matchFactory; } } } if (factory == null) { factory = new GeometryFactory(coordinateSystemId, axisCount, scales); factories.add(factory); } return factory; } } /** * <p> * Get a GeometryFactory with no coordinate system, 3D axis (x, y & z) and * a fixed x, y & floating z precision models. * </p> * * @param scaleXY The scale factor used to round the x, y coordinates. The * precision is 1 / scaleXy. A scale factor of 1000 will give a * precision of 1 / 1000 = 1mm for projected coordinate systems using * metres. * @return The geometry factory. */ public static GeometryFactory fixedNoSrid(final double... scales) { return fixed(0, scales); } /** * get a geometry factory with a floating scale. */ public static GeometryFactory floating(final CoordinateSystem coordinateSystem, final int axisCount) { if (coordinateSystem == null) { return floating(0, axisCount); } else { final int coordinateSystemId = coordinateSystem.getCoordinateSystemId(); if (coordinateSystemId <= 0) { final double[] scales = newScalesFloating(axisCount); return new GeometryFactory(coordinateSystem, axisCount, scales); } else { return floating(coordinateSystemId, axisCount); } } } /** * <p> * Get a GeometryFactory with the coordinate system, number of axis and a * floating precision model. * </p> * * @param coordinateSystemId The <a href="http://spatialreference.org/ref/epsg/">EPSG * coordinate system id</a>. * @param axisCount The number of coordinate axis. 2 for 2D x & y * coordinates. 3 for 3D x, y & z coordinates. * @return The geometry factory. */ public static GeometryFactory floating(final int coordinateSystemId, final int axisCount) { final double[] scales = newScalesFloating(axisCount); return fixed(coordinateSystemId, axisCount, scales); } /** * get a 3d geometry factory with a floating scale. */ public static GeometryFactory floating3(final CoordinateSystem coordinateSystem) { if (coordinateSystem == null) { return DEFAULT_3D; } else { final int coordinateSystemId = coordinateSystem.getCoordinateSystemId(); if (coordinateSystemId == 0) { return new GeometryFactory(coordinateSystem, 3, SCALES_FLOATING_3); } else { return floating3(coordinateSystemId); } } } /** * <p> * Get a GeometryFactory with the coordinate system, 3D axis (x, y & z) * and a floating precision models. * </p> * * @param coordinateSystemId The <a href="http://spatialreference.org/ref/epsg/">EPSG * coordinate system id</a>. * @return The geometry factory. */ public static GeometryFactory floating3(final int coordinateSystemId) { return fixed(coordinateSystemId, SCALES_FLOATING_3); } public static GeometryFactory get(final Object factory) { if (factory instanceof GeometryFactory) { return (GeometryFactory)factory; } else if (factory instanceof Map) { @SuppressWarnings("unchecked") final Map<String, Object> properties = (Map<String, Object>)factory; return newGeometryFactory(properties); } else { return null; } } public static String getAxisName(final int axisIndex) { switch (axisIndex) { case 0: return "X"; case 1: return "Y"; case 2: return "Z"; case 3: return "M"; default: return String.valueOf(axisIndex); } } /** * <p> * Get a GeometryFactory with the coordinate system, 3D axis (x, y & z) * and a floating precision models. * </p> * * @param coordinateSystemId The <a href="http://spatialreference.org/ref/epsg/">EPSG * coordinate system id</a>. * @return The geometry factory. */ public static GeometryFactory getFactory(final String wkt) { final CoordinateSystem esriCoordinateSystem = EsriCoordinateSystems.getCoordinateSystem(wkt); if (esriCoordinateSystem == null) { return DEFAULT_3D; } else { final CoordinateSystem epsgCoordinateSystem = EpsgCoordinateSystems .getCoordinateSystem(esriCoordinateSystem); final int coordinateSystemId = epsgCoordinateSystem.getCoordinateSystemId(); return floating(coordinateSystemId, 3); } } private static Set<DataType> getGeometryDataTypes( final Collection<? extends Geometry> geometries) { final Set<DataType> dataTypes = new LinkedHashSet<>(); for (final Geometry geometry : geometries) { final DataType dataType = geometry.getDataType(); dataTypes.add(dataType); } return dataTypes; } @SuppressWarnings("unchecked") public static <G extends Geometry> G newGeometry(final List<? extends Geometry> geometries) { if (geometries == null || geometries.size() == 0) { return (G)GeometryFactory.DEFAULT_3D.geometry(); } else { final GeometryFactory geometryFactory = geometries.get(0).getGeometryFactory(); return geometryFactory.geometry(geometries); } } public static GeometryFactory newGeometryFactory(final Map<String, Object> properties) { final int coordinateSystemId = Maps.getInteger(properties, "srid", 0); final int axisCount = Maps.getInteger(properties, "axisCount", 2); final double scaleXY = Maps.getDouble(properties, "scaleXy", 0.0); final double scaleX = Maps.getDouble(properties, "scaleX", scaleXY); final double scaleY = Maps.getDouble(properties, "scaleY", scaleXY); final double scaleZ = Maps.getDouble(properties, "scaleZ", 0.0); return GeometryFactory.fixed(coordinateSystemId, axisCount, scaleX, scaleY, scaleZ); } public static double[] newScalesFixed(final int axisCount, final double scale) { final double[] scales = new double[Math.max(axisCount, 2)]; Arrays.fill(scales, scale); return scales; } public static double[] newScalesFloating(final int axisCount) { if (axisCount < 3) { return SCALES_FLOATING_2; } else if (axisCount == 3) { return SCALES_FLOATING_3; } else { return new double[axisCount]; } } public static GeometryFactory newWithOffsets(final CoordinateSystem coordinateSystem, final double offsetX, final double scaleX, final double offsetY, final double scaleY, final double offsetZ, final double scaleZ) { if (offsetX == 0 && offsetY == 0 && offsetZ == 0) { return fixed(coordinateSystem, 3, scaleX, scaleY, scaleZ); } else { return new GeometryFactoryWithOffsets(coordinateSystem, offsetX, scaleX, offsetY, scaleY, offsetZ, scaleZ); } } public static GeometryFactory newWithOffsets(final int coordinateSystemId, final double offsetX, final double scaleX, final double offsetY, final double scaleY, final double offsetZ, final double scaleZ) { if (offsetX == 0 && offsetY == 0 && offsetZ == 0) { return fixed(coordinateSystemId, 3, scaleX, scaleY, scaleZ); } else { return new GeometryFactoryWithOffsets(coordinateSystemId, offsetX, scaleX, offsetY, scaleY, offsetZ, scaleZ); } } public static double toResolution(final double scale) { if (scale > 0) { return 1 / scale; } else { return 0; } } public static GeometryFactory wgs84() { return floating3(4326); } public static GeometryFactory worldMercator() { return floating3(3857); } private int axisCount = 2; private final BoundingBox boundingBoxEmpty = new BoundingBoxGeometryFactory(this); private final CoordinateSystem coordinateSystem; private final int coordinateSystemId; private final EmptyPoint emptyPoint = new EmptyPoint(); private transient final WktParser parser = new WktParser(this); protected double resolutionX = 0; protected double resolutionY = 0; protected double resolutionZ = 0; protected double[] scales; protected double scaleX = 0; protected double scaleY = 0; protected double scaleZ = 0; protected GeometryFactory(final CoordinateSystem coordinateSystem, final int axisCount, final double... scales) { this.coordinateSystem = coordinateSystem; if (coordinateSystem == null) { this.coordinateSystemId = 0; } else { this.coordinateSystemId = coordinateSystem.getCoordinateSystemId(); } init(axisCount, scales); } protected GeometryFactory(final CoordinateSystem coordinateSystem, final int coordinateSystemId, final int axisCount, final double... scales) { this.coordinateSystemId = coordinateSystemId; if (coordinateSystem == null && coordinateSystemId > 0) { this.coordinateSystem = EpsgCoordinateSystems.getCoordinateSystem(coordinateSystemId); } else { this.coordinateSystem = coordinateSystem; } init(axisCount, scales); } protected GeometryFactory(final int coordinateSystemId, final int axisCount, final double... scales) { this.coordinateSystemId = coordinateSystemId; if (coordinateSystemId > 0) { this.coordinateSystem = EpsgCoordinateSystems.getCoordinateSystem(coordinateSystemId); } else { this.coordinateSystem = null; } init(axisCount, scales); } public void addGeometries(final List<Geometry> geometryList, final Geometry geometry) { if (geometry != null && !geometry.isEmpty()) { for (final Geometry part : geometry.geometries()) { if (part != null && !part.isEmpty()) { geometryList.add(part.newGeometry(this)); } } } } /** * Build an appropriate <code>Geometry</code>, <code>MultiGeometry</code>, or * <code>GeometryCollection</code> to contain the <code>Geometry</code>s in * it. * For example:<br> * * <ul> * <li> If <code>geomList</code> contains a single <code>Polygon</code>, * the <code>Polygon</code> is returned. * <li> If <code>geomList</code> contains several <code>Polygon</code>s, a * <code>MultiPolygon</code> is returned. * <li> If <code>geomList</code> contains some <code>Polygon</code>s and * some <code>LineString</code>s, a <code>GeometryCollection</code> is * returned. * <li> If <code>geomList</code> is empty, an empty <code>GeometryCollection</code> * is returned * </ul> * * Note that this method does not "flatten" Geometries in the input, and hence if * any MultiGeometries are contained in the input a GeometryCollection containing * them will be returned. * *@param geometries the <code>Geometry</code>s to combine *@return a <code>Geometry</code> of the "smallest", "most * type-specific" class that can contain the elements of <code>geomList</code> * . */ public Geometry buildGeometry(final Iterable<? extends Geometry> geometries) { DataType collectionDataType = null; boolean isHeterogeneous = false; boolean hasGeometryCollection = false; final List<Geometry> geometryList = new ArrayList<>(); for (final Geometry geometry : geometries) { if (geometry != null) { geometryList.add(geometry); DataType geometryDataType = geometry.getDataType(); if (geometry instanceof LinearRing) { geometryDataType = DataTypes.LINE_STRING; } if (collectionDataType == null) { collectionDataType = geometryDataType; } else if (geometryDataType != collectionDataType) { isHeterogeneous = true; } if (geometry.isGeometryCollection()) { hasGeometryCollection = true; } } } /** * Now construct an appropriate geometry to return */ if (collectionDataType == null) { return geometryCollection(); } else if (isHeterogeneous || hasGeometryCollection) { return geometryCollection(geometryList); } else if (geometryList.size() == 1) { return geometryList.iterator().next(); } else if (DataTypes.POINT.equals(collectionDataType)) { return punctual(geometryList); } else if (DataTypes.LINE_STRING.equals(collectionDataType)) { return lineal(geometryList); } else if (DataTypes.POLYGON.equals(collectionDataType)) { return polygonal(geometryList); } else { throw new IllegalArgumentException("Unknown geometry type " + collectionDataType); } } @Override public GeometryFactory clone() { return this; } public GeometryFactory convertAxisCount(final int axisCount) { if (axisCount == getAxisCount()) { return this; } else { return fixed(this.coordinateSystem, this.coordinateSystemId, axisCount, this.scales); } } public GeometryFactory convertAxisCountAndScales(final int axisCount, final double... scales) { return fixed(this.coordinateSystem, this.coordinateSystemId, axisCount, scales); } public GeometryFactory convertCoordinateSystem(final CoordinateSystem coordinateSystem) { if (isSameCoordinateSystem(coordinateSystem)) { return this; } else { return GeometryFactory.fixed(coordinateSystem, this.axisCount, this.scales); } } public GeometryFactory convertScales(final double... scales) { return fixed(this.coordinateSystem, this.coordinateSystemId, this.axisCount, scales); } public GeometryFactory convertSrid(final int coordinateSystemId) { if (coordinateSystemId == getCoordinateSystemId()) { return this; } else { return GeometryFactory.fixed(coordinateSystemId, this.axisCount, this.scales); } } public double[] copyPrecise(final double[] values) { final double[] valuesPrecise = new double[values.length]; makePrecise(values, valuesPrecise); return valuesPrecise; } private boolean equalsScales(final double[] scales) { final int minLength = Math.min(this.scales.length, scales.length); for (int i = 0; i < minLength; i++) { final double scale1 = this.scales[i]; final double scale2 = scales[i]; if (scale1 != scale2) { return false; } } return true; } public Geometry geometry() { return this.emptyPoint; } /** * <p> * Construct a new new geometry of the requested target geometry class. * <p> * * @param targetClass * @param geometry * @return */ @SuppressWarnings({ "unchecked" }) public <V extends Geometry> V geometry(final Class<?> targetClass, Geometry geometry) { if (geometry != null && !geometry.isEmpty()) { geometry = geometry.newGeometry(this); if (geometry.isGeometryCollection()) { if (geometry.getGeometryCount() == 1) { geometry = geometry.getGeometry(0); } else { geometry = geometry.union(); // Union doesn't use this geometry factory geometry = geometry.newGeometry(this); } } final Class<?> geometryClass = geometry.getClass(); if (targetClass.isAssignableFrom(geometryClass)) { // TODO if geometry collection then clean up return (V)geometry; } else if (Point.class.isAssignableFrom(targetClass)) { if (geometry.getGeometryCount() == 1) { final Geometry part = geometry.getGeometry(0); if (part instanceof Point) { return (V)part; } } } else if (LineString.class.isAssignableFrom(targetClass)) { if (geometry.getGeometryCount() == 1) { final Geometry part = geometry.getGeometry(0); if (part instanceof LineString) { return (V)part; } } else { final List<LineString> mergedLineStrings = LineMerger.merge(geometry); if (mergedLineStrings.size() == 1) { return (V)mergedLineStrings.get(0); } } } else if (Polygon.class.isAssignableFrom(targetClass)) { if (geometry.getGeometryCount() == 1) { final Geometry part = geometry.getGeometry(0); if (part instanceof Polygon) { return (V)part; } } } else if (Punctual.class.isAssignableFrom(targetClass)) { if (geometry instanceof Punctual) { return (V)geometry; } } else if (Lineal.class.isAssignableFrom(targetClass)) { if (geometry instanceof Lineal) { return (V)geometry; } } else if (Polygonal.class.isAssignableFrom(targetClass)) { if (geometry instanceof Polygonal) { return (V)polygonal(geometry); } } } return null; } /** * Construct a new new geometry by flattening the input geometries, ignoring and null or empty * geometries. If there are no geometries, then an empty {@link Geometry} will be returned. * If there is one geometry that single geometry will be returned. Otherwise the result * will be a subclass of {@link GeometryCollection}. * * @author Paul Austin <paul.austin@revolsys.com> * @param geometries * @return */ @SuppressWarnings("unchecked") public <V extends Geometry> V geometry(final Collection<? extends Geometry> geometries) { final List<Geometry> geometryList = getGeometries(geometries); if (geometryList == null || geometryList.size() == 0) { return (V)geometryCollection(); } else if (geometryList.size() == 1) { return (V)geometryList.get(0); } else { final Set<DataType> dataTypes = getGeometryDataTypes(geometryList); if (dataTypes.size() == 1) { final DataType dataType = CollectionUtil.get(dataTypes, 0); if (dataType.equals(DataTypes.POINT)) { return (V)punctual(geometryList); } else if (dataType.equals(DataTypes.LINE_STRING)) { return (V)lineal(geometryList); } else if (dataType.equals(DataTypes.POLYGON)) { return (V)polygonal(geometryList); } } return (V)geometryCollection(geometries); } } @SuppressWarnings("unchecked") public <V extends Geometry> V geometry(final Geometry... geometries) { return (V)geometry(Arrays.asList(geometries)); } /** * Creates a deep copy of the input {@link Geometry}. * <p> * This is a convenient way to change the <tt>LineString</tt> * used to represent a geometry, or to change the * factory used for a geometry. * <p> * {@link Geometry#clone()} can also be used to make a deep copy, * but it does not allow changing the LineString type. * * @return a deep copy of the input geometry, using the LineString type of this factory * * @see Geometry#clone() */ public Geometry geometry(final Geometry geometry) { if (geometry == null) { return null; } else { final int coordinateSystemId = getCoordinateSystemId(); final int geometrySrid = geometry.getCoordinateSystemId(); if (coordinateSystemId == 0 && geometrySrid != 0) { final GeometryFactory geometryFactory = GeometryFactory.fixed(geometrySrid, this.axisCount, getScaleX(), getScaleY(), getScaleZ()); return geometryFactory.geometry(geometry); } else if (coordinateSystemId != 0 && geometrySrid != 0 && geometrySrid != coordinateSystemId) { if (geometry instanceof Point) { return geometry.newGeometry(this); } else if (geometry instanceof LineString) { return geometry.newGeometry(this); } else if (geometry instanceof Polygon) { return geometry.newGeometry(this); } else if (geometry instanceof Punctual) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return punctual(geometries); } else if (geometry instanceof Lineal) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return lineal(geometries); } else if (geometry instanceof Polygonal) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return polygonal(geometries); } else if (geometry.isGeometryCollection()) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return geometryCollection(geometries); } else { return geometry.newGeometry(this); } } else if (geometry instanceof Point) { final Point point = (Point)geometry; return point.newGeometry(this); } else if (geometry instanceof LinearRing) { final LinearRing linearRing = (LinearRing)geometry; return linearRing.newGeometry(this); } else if (geometry instanceof LineString) { final LineString lineString = (LineString)geometry; return lineString.newGeometry(this); } else if (geometry instanceof Polygon) { final Polygon polygon = (Polygon)geometry; return polygon(polygon); } else if (geometry instanceof Punctual) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return punctual(geometries); } else if (geometry instanceof Lineal) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return lineal(geometries); } else if (geometry instanceof Polygonal) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return polygonal(geometries); } else if (geometry instanceof GeometryCollection) { final List<Geometry> geometries = new ArrayList<>(); addGeometries(geometries, geometry); return geometryCollection(geometries); } else { return null; } } } @SuppressWarnings("unchecked") public <T extends Geometry> T geometry(final String wkt) { if (Property.hasValue(wkt)) { return (T)this.parser.parseGeometry(wkt); } else { return null; } } @SuppressWarnings("unchecked") public <T extends Geometry> T geometry(final String wkt, final boolean useAxisCountFromGeometryFactory) { return (T)this.parser.parseGeometry(wkt, useAxisCountFromGeometryFactory); } public Geometry geometryCollection() { return new GeometryCollectionImpl(this); } /** * Does not flattern nested geometry collections. * * @param geometries * @return */ @SuppressWarnings("unchecked") public <G extends Geometry> G geometryCollection(final Iterable<? extends Geometry> geometries) { if (geometries == null) { return (G)geometryCollection(); } else { DataType dataType = null; boolean heterogeneous = false; final List<Geometry> geometryList = new ArrayList<>(); if (geometries != null) { for (final Geometry geometry : geometries) { if (geometry != null) { if (heterogeneous) { } else { final DataType geometryDataType = geometry.getDataType(); if (dataType == null) { dataType = geometryDataType; } else if (dataType != geometryDataType) { heterogeneous = true; dataType = null; } } final Geometry copy = geometry.newGeometry(this); geometryList.add(copy); } } } if (geometryList.size() == 0) { return (G)geometryCollection(); } else if (geometryList.size() == 1) { return (G)geometryList.get(0); } else if (dataType == DataTypes.POINT) { return (G)punctual(geometryList); } else if (dataType == DataTypes.LINE_STRING) { return (G)lineal(geometryList); } else if (dataType == DataTypes.POLYGON) { return (G)polygonal(geometryList); } else { final Geometry[] geometryArray = new Geometry[geometryList.size()]; geometryList.toArray(geometryArray); return (G)new GeometryCollectionImpl(this, geometryArray); } } } public int getAxisCount() { return this.axisCount; } public Point getCoordinates(final Point point) { final Point convertedPoint = project(point); return convertedPoint; } /** * <p>Get the {@link CoordinatesOperation} to convert between this factory's and the other factory's * {@link CoordinateSystem}.</p> * * @param geometryFactory The geometry factory to convert to. * @return The coordinates operation or null if no conversion is available. */ @Override public CoordinatesOperation getCoordinatesOperation(final GeometryFactory geometryFactory) { if (geometryFactory == this) { return null; } else if (geometryFactory == null) { return null; } else if (!geometryFactory.isHasCoordinateSystem()) { return null; } else if (!isHasCoordinateSystem()) { return null; } else { if (hasSameCoordinateSystem(geometryFactory)) { return null; } else { final CoordinateSystem coordinateSystem = getCoordinateSystem(); final CoordinateSystem otherCoordinateSystem = geometryFactory.getCoordinateSystem(); return ProjectionFactory.getCoordinatesOperation(coordinateSystem, otherCoordinateSystem); } } } @Override public CoordinateSystem getCoordinateSystem() { return this.coordinateSystem; } @Override public int getCoordinateSystemId() { return this.coordinateSystemId; } public GeometryFactory getGeographicGeometryFactory() { if (this.coordinateSystem instanceof GeographicCoordinateSystem) { return this; } else if (this.coordinateSystem instanceof ProjectedCoordinateSystem) { final ProjectedCoordinateSystem projectedCs = (ProjectedCoordinateSystem)this.coordinateSystem; final GeographicCoordinateSystem geographicCs = projectedCs.getGeographicCoordinateSystem(); final int coordinateSystemId = geographicCs.getCoordinateSystemId(); return floating(coordinateSystemId, getAxisCount()); } else { return floating(4326, getAxisCount()); } } public List<Geometry> getGeometries(final Collection<? extends Geometry> geometries) { final List<Geometry> geometryList = new ArrayList<>(); for (final Geometry geometry : geometries) { addGeometries(geometryList, geometry); } return geometryList; } @Override public GeometryFactory getGeometryFactory() { return this; } private LinearRing getLinearRing(final List<?> rings, final int index) { final Object ring = rings.get(index); if (ring instanceof LinearRing) { return (LinearRing)ring; } else if (ring instanceof LineString) { final LineString points = (LineString)ring; return linearRing(points); } else if (ring instanceof LineString) { final LineString line = (LineString)ring; final LineString points = line; return linearRing(points); } else if (ring instanceof double[]) { final double[] coordinates = (double[])ring; return linearRing(getAxisCount(), coordinates); } else { return null; } } /** * Returns the maximum number of significant digits provided by this * precision model. * Intended for use by routines which need to print out * decimal representations of precise values . * <p> * This method would be more correctly called * <tt>getMinimumDecimalPlaces</tt>, * since it actually computes the number of decimal places * that is required to correctly display the full * precision of an ordinate value. * <p> * Since it is difficult to compute the required number of * decimal places for scale factors which are not powers of 10, * the algorithm uses a very rough approximation in this case. * This has the side effect that for scale factors which are * powers of 10 the value returned is 1 greater than the true value. * * * @return the maximum number of decimal places provided by this precision model */ public int getMaximumSignificantDigits() { int maxSigDigits = 16; if (isFloating()) { maxSigDigits = 16; } else { maxSigDigits = 1 + (int)Math.ceil(Math.log(this.scaleX) / Math.log(10)); } return maxSigDigits; } public double getOffset(final int axisIndex) { return 0; } public double getOffsetX() { return 0; } public double getOffsetY() { return 0; } public double getOffsetZ() { return 0; } public Point[] getPointArray(final Iterable<?> pointsList) { final List<Point> points = new ArrayList<>(); for (final Object object : pointsList) { final Point point = point(object); if (point != null && !point.isEmpty()) { points.add(point); } } return points.toArray(new Point[points.size()]); } @SuppressWarnings("unchecked") public Polygon[] getPolygonArray(final Iterable<?> polygonList) { final List<Polygon> polygons = new ArrayList<>(); for (final Object value : polygonList) { Polygon polygon; if (value instanceof Polygon) { polygon = (Polygon)value; } else if (value instanceof List) { final List<LineString> coordinateList = (List<LineString>)value; polygon = polygon(coordinateList); } else if (value instanceof LineString) { final LineString coordinateList = (LineString)value; polygon = polygon(coordinateList); } else { polygon = null; } if (polygon != null) { polygons.add(polygon); } } return polygons.toArray(new Polygon[polygons.size()]); } public Point[] getPrecise(final Point... points) { final Point[] precisesPoints = new Point[points.length]; for (int i = 0; i < points.length; i++) { final Point point = points[i]; precisesPoints[i] = getPreciseCoordinates(point); } return precisesPoints; } public Point getPreciseCoordinates(final Point point) { final double[] coordinates = point.getCoordinates(); makePrecise(coordinates.length, coordinates); return new PointDouble(coordinates); } public double getResolution(final int axisIndex) { final double scale = getScale(axisIndex); if (scale <= 0) { return 0; } else { return 1 / scale; } } public double getResolutionX() { return this.resolutionX; } public double getResolutionXy() { return this.resolutionX; } public double getResolutionY() { return this.resolutionY; } public double getResolutionZ() { return this.resolutionZ; } public double getScale(final int axisIndex) { switch (axisIndex) { case 0: return this.scaleX; case 1: return this.scaleY; case 2: return this.scaleZ; default: if (axisIndex < 0 || axisIndex >= this.scales.length) { return 0; } else { return this.scales[axisIndex - 1]; } } } public double getScaleX() { return this.scaleX; } public double getScaleXY() { return this.scaleX; } public double getScaleY() { return this.scaleY; } public double getScaleZ() { return this.scaleZ; } @Override public int hashCode() { return this.coordinateSystemId; } public boolean hasM() { return this.axisCount > 3; } public boolean hasSameCoordinateSystem(final GeometryFactory geometryFactory) { if (geometryFactory == null) { return false; } else { final int coordinateSystemId1 = getCoordinateSystemId(); final int coordinateSystemId2 = geometryFactory.getCoordinateSystemId(); if (coordinateSystemId1 == coordinateSystemId2) { if (coordinateSystemId1 >= 0) { return true; } } final CoordinateSystem coordinateSystem1 = getCoordinateSystem(); final CoordinateSystem coordinateSystem2 = geometryFactory.getCoordinateSystem(); if (coordinateSystem1 == coordinateSystem2) { return true; } else if (coordinateSystem1 == null || coordinateSystem2 == null) { return false; } else if (coordinateSystem1.equals(coordinateSystem2)) { return true; } else { return false; } } } public boolean hasZ() { return this.axisCount > 2; } protected void init(final int axisCount, final double... scales) { this.axisCount = Math.max(axisCount, 2); this.scales = new double[axisCount]; for (int axisIndex = 0; axisIndex < axisCount && axisIndex < scales.length; axisIndex++) { final double scale = scales[axisIndex]; this.scales[axisIndex] = scale; } this.scaleX = this.scales[0]; this.resolutionX = toResolution(this.scaleX); this.scaleY = this.scales[1]; this.resolutionY = toResolution(this.scaleY); if (axisCount > 2) { this.scaleZ = this.scales[2]; this.resolutionZ = toResolution(this.scaleZ); } } public boolean isFloating() { return this.scaleX == 0; } public boolean isGeographics() { return this.coordinateSystem instanceof GeographicCoordinateSystem; } public boolean isHasCoordinateSystem() { return this.coordinateSystem != null; } public boolean isProjected() { return this.coordinateSystem instanceof ProjectedCoordinateSystem; } public boolean isSameCoordinateSystem(final CoordinateSystem coordinateSystem) { final int coordinateSystemId = getCoordinateSystemId(); if (coordinateSystem == null) { return this.coordinateSystem == null; } else { final int coordinateSystemId2 = coordinateSystem.getCoordinateSystemId(); if (coordinateSystemId == coordinateSystemId2) { return true; } else { final CoordinateSystem coordinateSystem2 = this.coordinateSystem; if (coordinateSystem2 == null) { if (coordinateSystemId2 <= 0) { return true; } else if (coordinateSystemId <= 0) { return true; } else { return false; } } else { return coordinateSystem.equals(coordinateSystem2); } } } } @Override public boolean isSameCoordinateSystem(final GeometryFactory geometryFactory) { if (geometryFactory == null) { return false; } else { final int coordinateSystemId = getCoordinateSystemId(); final int coordinateSystemId2 = geometryFactory.getCoordinateSystemId(); if (coordinateSystemId == coordinateSystemId2) { return true; } else { final CoordinateSystem coordinateSystem = getCoordinateSystem(); final CoordinateSystem coordinateSystem2 = geometryFactory.getCoordinateSystem(); if (coordinateSystem == null) { if (coordinateSystemId <= 0) { return true; } else if (coordinateSystem2 == null && coordinateSystemId2 <= 0) { return true; } else { return false; } } else if (coordinateSystem2 == null) { if (coordinateSystemId2 <= 0) { return true; } else if (coordinateSystemId <= 0) { return true; } else { return false; } } else { return coordinateSystem.equals(coordinateSystem2); } } } } public Lineal lineal(final Geometry geometry) { if (geometry instanceof Lineal) { final Lineal lineal = (Lineal)geometry; return lineal.convertGeometry(this); } else if (geometry.isGeometryCollection()) { final List<LineString> lines = new ArrayList<>(); for (final Geometry part : geometry.geometries()) { if (part instanceof LineString) { lines.add((LineString)part); } else { throw new IllegalArgumentException( "Cannot convert class " + part.getGeometryType() + " to Lineal\n" + geometry); } } return lineal(lines); } else { throw new IllegalArgumentException( "Cannot convert class " + geometry.getGeometryType() + " to Lineal\n" + geometry); } } public Lineal lineal(final Geometry... lines) { return lineal(Arrays.asList(lines)); } public Lineal lineal(final int axisCount, final double[]... linesCoordinates) { if (linesCoordinates == null) { return lineString(); } else { final int lineCount = linesCoordinates.length; final LineString[] lines = new LineString[lineCount]; for (int i = 0; i < lineCount; i++) { final double[] coordinates = linesCoordinates[i]; lines[i] = lineString(axisCount, coordinates); } return lineal(lines); } } public Lineal lineal(final Iterable<?> lines) { if (Property.isEmpty(lines)) { return lineString(); } else { final List<LineString> lineStrings = new ArrayList<>(); for (final Object value : lines) { if (value instanceof LineString) { final LineString line = (LineString)value; lineStrings.add(line.convertGeometry(this)); } else if (value instanceof Lineal) { for (final LineString line : ((Lineal)value).lineStrings()) { lineStrings.add(line.convertGeometry(this)); } } else if (value instanceof double[]) { final double[] points = (double[])value; final int axisCount = getAxisCount(); final LineString line = lineString(axisCount, points); lineStrings.add(line); } } final int lineCount = lineStrings.size(); if (lineCount == 0) { return lineString(); } else if (lineCount == 1) { return lineStrings.get(0); } else { final LineString[] lineArray = new LineString[lineCount]; lineStrings.toArray(lineArray); return lineal(lineArray); } } } /** * Creates a MultiLineString using the given LineStrings; a null or empty * array will Construct a new empty MultiLineString. * * @param lineStrings LineStrings, each of which may be empty but not null * @return the created MultiLineString */ public Lineal lineal(final LineString... lines) { if (lines == null || lines.length == 0) { return lineString(); } else if (lines.length == 1) { return lines[0]; } else { return new MultiLineStringImpl(this, lines); } } public LinearRing linearRing() { return new LinearRingDoubleGf(this); } public LinearRing linearRing(final Collection<?> points) { if (points.isEmpty()) { return linearRing(); } else { final LineStringDoubleBuilder lineBuilder = newLineStringBuilder(points); return lineBuilder.newLinearRing(); } } public LinearRing linearRing(final int axisCount, double... coordinates) { final int vertexCount = coordinates.length / axisCount; coordinates = LineStringDoubleGf.getNewCoordinates(this, axisCount, vertexCount, coordinates); return new LinearRingDoubleGf(this, this.axisCount, vertexCount, coordinates); } public LinearRing linearRing(final int axisCount, final int vertexCount, double... coordinates) { coordinates = LineStringDoubleGf.getNewCoordinates(this, axisCount, vertexCount, coordinates); return new LinearRingDoubleGf(this, this.axisCount, vertexCount, coordinates); } /** * Creates a {@link LinearRing} using the given {@link LineString}. * A null or empty array creates an empty LinearRing. * The points must form a closed and simple linestring. * * @param coordinates a LineString (possibly empty), or null * @return the created LinearRing * @throws IllegalArgumentException if the ring is not closed, or has too few points */ public LinearRing linearRing(final LineString line) { if (line == null || line.isEmpty()) { return linearRing(); } else { final int vertexCount = line.getVertexCount(); final double[] coordinates = LineStringDoubleGf.getNewCoordinates(this, line); return new LinearRingDoubleGf(this, this.axisCount, vertexCount, coordinates); } } /** * Creates a {@link LinearRing} using the given {@link Coordinates}s. * A null or empty array creates an empty LinearRing. * The points must form a closed and simple linestring. * @param coordinates an array without null elements, or an empty array, or null * @return the created LinearRing * @throws IllegalArgumentException if the ring is not closed, or has too few points */ public LinearRing linearRing(final Point... points) { if (points == null || points.length == 0) { return linearRing(); } else { return linearRing(Arrays.asList(points)); } } public LineSegment lineSegment(final Point p0, final Point p1) { return new LineSegmentDoubleGF(this, p0, p1); } public LineString lineString() { return new LineStringDoubleGf(this); } public LineString lineString(final Collection<?> points) { if (points == null || points.isEmpty()) { return lineString(); } else { final LineStringDoubleBuilder lineBuilder = newLineStringBuilder(points); return lineBuilder.newLineString(); } } public LineString lineString(final int axisCount, double... coordinates) { if (coordinates == null || coordinates.length == 1) { return lineString(); } else if (axisCount < 2) { return lineString(); } else { final int vertexCount = coordinates.length / axisCount; coordinates = LineStringDoubleGf.getNewCoordinates(this, axisCount, vertexCount, coordinates); return new LineStringDoubleGf(this, this.axisCount, vertexCount, coordinates); } } public LineString lineString(final int axisCount, final int vertexCount, double... coordinates) { coordinates = LineStringDoubleGf.getNewCoordinates(this, axisCount, vertexCount, coordinates); return new LineStringDoubleGf(this, this.axisCount, vertexCount, coordinates); } public LineString lineString(final int axisCount, final Number[] coordinates) { final int vertexCount = coordinates.length / axisCount; final double[] coordinatesDouble = LineStringDoubleGf.getNewCoordinates(this, axisCount, vertexCount, coordinates); return new LineStringDoubleGf(this, this.axisCount, vertexCount, coordinatesDouble); } public LineString lineString(final LineString line) { if (line == null || line.isEmpty()) { return lineString(); } else { final int vertexCount = line.getVertexCount(); final double[] coordinates = LineStringDoubleGf.getNewCoordinates(this, line); return new LineStringDoubleGf(this, this.axisCount, vertexCount, coordinates); } } public LineString lineString(final Point... points) { if (points == null) { return lineString(); } else { final List<Point> linePoints = new ArrayList<>(); for (final Point point : points) { if (point != null && !point.isEmpty()) { linePoints.add(point); } } return lineString(linePoints); } } public void makePrecise(final double[] values, final double[] valuesPrecise) { for (int i = 0; i < valuesPrecise.length; i++) { final int axisIndex = i % this.axisCount; valuesPrecise[i] = makePrecise(axisIndex, values[i]); } } public double makePrecise(final int axisIndex, final double value) { final double scale = getScale(axisIndex); if (scale > 0 && Double.isFinite(value)) { final double multiple = value * scale; final double scaledValue = Math.round(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public void makePrecise(final int axisCount, final double... coordinates) { for (int i = 0; i < coordinates.length; i++) { final double value = coordinates[i]; final int axisIndex = i % axisCount; final double scale = getScale(axisIndex); if (scale > 0) { final double multiple = value * scale; final long scaledValue = Math.round(multiple); final double preciseValue = scaledValue / scale; coordinates[i] = preciseValue; } } } public double makePreciseCeil(final int axisIndex, final double value) { final double scale = getScale(axisIndex); if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.ceil(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makePreciseFloor(final int axisIndex, final double value) { final double scale = getScale(axisIndex); if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.floor(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeXPrecise(final double value) { final double scale = this.scaleX; if (scale > 0 && Double.isFinite(value)) { final double multiple = value * scale; final double scaledValue = Math.round(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeXPreciseCeil(final double value) { final double scale = this.scaleX; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.ceil(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeXPreciseFloor(final double value) { final double scale = this.scaleX; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.floor(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeXyPrecise(final double value) { final double scale = this.scaleX; if (scale > 0 && Double.isFinite(value)) { final double multiple = value * scale; final double scaledValue = Math.round(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeXyPreciseCeil(final double value) { final double scale = this.scaleX; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.ceil(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeXyPreciseFloor(final double value) { final double scale = this.scaleX; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.floor(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeYPrecise(final double value) { final double scale = this.scaleY; if (scale > 0 && Double.isFinite(value)) { final double multiple = value * scale; final double scaledValue = Math.round(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeYPreciseCeil(final double value) { final double scale = this.scaleY; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.ceil(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeYPreciseFloor(final double value) { final double scale = this.scaleY; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.floor(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeZPrecise(final double value) { final double scale = this.scaleZ; if (scale > 0 && Double.isFinite(value)) { final double multiple = value * scale; final double scaledValue = Math.round(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeZPreciseCeil(final double value) { final double scale = this.scaleZ; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.ceil(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public double makeZPreciseFloor(final double value) { final double scale = this.scaleZ; if (scale > 0) { final double multiple = value * scale; final long scaledValue = (long)Math.floor(multiple); final double preciseValue = scaledValue / scale; return preciseValue; } else { return value; } } public BoundingBox newBoundingBox(final double x, final double y) { return new BoundingBoxDoubleXYGeometryFactory(this, x, y); } public BoundingBox newBoundingBox(final double minX, final double minY, final double maxX, final double maxY) { return new BoundingBoxDoubleXYGeometryFactory(this, minX, minY, maxX, maxY); } public BoundingBox newBoundingBox(final int axisCount) { return new BoundingBoxDoubleGf(this, axisCount); } public BoundingBox newBoundingBox(final int axisCount, final double... bounds) { if (axisCount == 2) { final double x1 = bounds[0]; final double y1 = bounds[1]; final double x2 = bounds[2]; final double y2 = bounds[3]; return new BoundingBoxDoubleXYGeometryFactory(this, x1, y1, x2, y2); } else { return new BoundingBoxDoubleGf(this, axisCount, bounds); } } public BoundingBox newBoundingBox(int axisCount, final Iterable<? extends Point> points) { axisCount = Math.min(axisCount, getAxisCount()); double[] bounds = null; if (points != null) { for (final Point point : points) { if (point != null) { if (bounds == null) { bounds = BoundingBoxUtil.newBounds(this, axisCount, point); } else { BoundingBoxUtil.expand(this, bounds, point); } } } } if (bounds == null) { return this.boundingBoxEmpty; } else { return newBoundingBox(axisCount, bounds); } } public BoundingBox newBoundingBox(int axisCount, final Point... points) { axisCount = Math.min(axisCount, getAxisCount()); double[] bounds = null; if (points != null) { for (final Point point : points) { if (point != null) { if (bounds == null) { bounds = BoundingBoxUtil.newBounds(this, axisCount, point); } else { BoundingBoxUtil.expand(this, bounds, point); } } } } if (bounds == null) { return this.boundingBoxEmpty; } else { return newBoundingBox(axisCount, bounds); } } public BoundingBox newBoundingBox(final Iterable<? extends Point> points) { double minX = Double.POSITIVE_INFINITY; double maxX = Double.NEGATIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY; for (final Point point : points) { final double x = point.getX(); final double y = point.getY(); if (x < minX) { minX = x; } if (y < minY) { minY = y; } if (x > maxX) { maxX = x; } if (y > maxY) { maxY = y; } } final boolean nullX = minX > maxX; final boolean nullY = minY > maxY; if (nullX) { if (nullY) { return this.boundingBoxEmpty; } else { return new BoundingBoxDoubleXYGeometryFactory(this, Double.NEGATIVE_INFINITY, minY, Double.POSITIVE_INFINITY, maxY); } } else { if (nullY) { return new BoundingBoxDoubleXYGeometryFactory(this, minX, Double.NEGATIVE_INFINITY, maxX, Double.POSITIVE_INFINITY); } else { return new BoundingBoxDoubleXYGeometryFactory(this, minX, minY, maxX, maxY); } } } public BoundingBox newBoundingBox(final Point point) { final double x = point.getX(); final double y = point.getY(); return newBoundingBox(x, y); } public BoundingBox newBoundingBoxEmpty() { return this.boundingBoxEmpty; } public double[] newBounds(final int axisCount) { return BoundingBoxUtil.newBounds(axisCount); } public LineStringDoubleBuilder newLineStringBuilder() { return new LineStringDoubleBuilder(this); } private LineStringDoubleBuilder newLineStringBuilder(final Collection<?> points) { final LineStringDoubleBuilder lineBuilder = new LineStringDoubleBuilder(this, points.size()); for (final Object object : points) { if (object == null) { } else if (object instanceof Point) { final Point point = (Point)object; lineBuilder.appendVertex(point); } else if (object instanceof double[]) { final double[] coordinates = (double[])object; lineBuilder.appendVertex(coordinates); } else if (object instanceof List<?>) { @SuppressWarnings("unchecked") final List<Number> list = (List<Number>)object; final double[] coordinates = MathUtil.toDoubleArray(list); lineBuilder.appendVertex(coordinates); } else if (object instanceof LineString) { final LineString LineString = (LineString)object; final Point point = LineString.getPoint(0); lineBuilder.appendVertex(point); } else { throw new IllegalArgumentException("Unexepected data type: " + object); } } return lineBuilder; } /** * <p>Construct a newn empty {@link Point}.</p> * * @return The point. */ public Point point() { return this.emptyPoint; } /** * <p>Construct a new new {@link Point} from the specified point coordinates. * If the point is null or has length < 2 an empty point will be returned. * The result point will have the same {@link #getAxisCount()} from this factory. * Additional coordinates in the point will be ignored. If the point length is < * {@link #getAxisCount()} then {@link Double#NaN} will be used for that axis.</p> * * @param point The coordinates to create the point from. * @return The point. */ public Point point(final double... coordinates) { if (coordinates == null || coordinates.length < 2) { return point(); } else if (this.axisCount == 2) { return new PointDoubleXYGeometryFactory(this, coordinates[0], coordinates[1]); } else { return new PointDoubleGf(this, coordinates); } } public Point point(final double x, final double y) { return new PointDoubleXYGeometryFactory(this, x, y); } /** * Creates a Point using the given LineString; a null or empty * LineString will Construct a newn empty Point. * * @param points a LineString (possibly empty), or null * @return the created Point */ public Point point(final LineString points) { if (points == null) { return point(); } else { final int size = points.getVertexCount(); if (size == 0) { return point(); } else if (size == 1) { final int axisCount = Math.min(points.getAxisCount(), getAxisCount()); final double[] coordinates = new double[axisCount]; for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) { final double coordinate = points.getCoordinate(0, axisIndex); coordinates[axisIndex] = coordinate; } return point(coordinates); } else { throw new IllegalArgumentException("Point can only have 1 vertex not " + size); } } } /** * <p>Construct a new new {@link Point} from the object using the following rules.<p> * <ul> * <li><code>null</code> using {@link #point()}</li> * <li>Instances of {@link Point} using {@link Point#newGeometry(GeometryFactory)}</li> * <li>Instances of {@link Coordinates} using {@link #point(Point)}</li> * <li>Instances of {@link LineString} using {@link #point(LineString)}</li> * <li>Instances of {@link double[]} using {@link #point(double[])}</li> * <li>Instances of any other class throws {@link IllegalArgumentException}.<li> * </ul> * * @param point The coordinates to create the point from. * @return The point. * @throws IllegalArgumentException If the object is not an instance of a supported class. */ public Point point(final Object object) { if (object == null) { return point(); } else if (object instanceof Point) { final Point point = (Point)object; return point.newGeometry(this); } else if (object instanceof double[]) { return point((double[])object); } else if (object instanceof List<?>) { @SuppressWarnings("unchecked") final List<Number> list = (List<Number>)object; final double[] pointCoordinates = MathUtil.toDoubleArray(list); return point(pointCoordinates); } else if (object instanceof Point) { return point((Point)object); } else if (object instanceof LineString) { return point((LineString)object); } else { throw new IllegalArgumentException("Cannot Construct a new point from " + object.getClass()); } } /** * <p>Construct a new new {@link Point} from the specified point ({@link Coordinates}). * If the point is null or has {@link Coordinates#getAxisCount()} < 2 an empty * point will be returned. The result point will have the same {@link #getAxisCount()} from this * factory. Additional axis in the point will be ignored. If the point has a smaller * {@link Point#getAxisCount()} then {@link Double#NaN} will be used for that axis.</p> * * @param point The coordinates to create the point from. * @return The point. */ public Point point(final Point point) { if (point == null || point.isEmpty()) { return point(); } else { if (point.isSameCoordinateSystem(this)) { final double[] coordinates = point.getCoordinates(); return point(coordinates); } else { return point.newGeometry(this); } } } public PolygonImpl polygon() { return new PolygonImpl(this); } public Polygon polygon(final Geometry... rings) { return polygon(Arrays.asList(rings)); } public Polygon polygon(final int axisCount, final double... ringCoordinates) { if (ringCoordinates == null) { return polygon(); } else { final LinearRing[] rings = { linearRing(axisCount, ringCoordinates) }; return new PolygonImpl(this, rings); } } public Polygon polygon(final int axisCount, final double[]... ringsCoordinates) { if (ringsCoordinates == null) { return polygon(); } else { final int ringCount = ringsCoordinates.length; final LinearRing[] rings = new LinearRing[ringCount]; for (int i = 0; i < ringCount; i++) { final double[] ringCoordinates = ringsCoordinates[i]; rings[i] = linearRing(axisCount, ringCoordinates); } return new PolygonImpl(this, rings); } } /** * Constructs a <code>Polygon</code> with the given exterior boundary. * * @param shell * the outer boundary of the new <code>Polygon</code>, or * <code>null</code> or an empty <code>LinearRing</code> if * the empty geometry is to be created. * @throws IllegalArgumentException if the boundary ring is invalid */ public Polygon polygon(final LinearRing shell) { return new PolygonImpl(this, shell); } public Polygon polygon(final LineString... rings) { final List<LineString> ringList = Arrays.asList(rings); return polygon(ringList); } public Polygon polygon(final List<?> rings) { if (rings.size() == 0) { return polygon(); } else { final LinearRing[] linearRings = new LinearRing[rings.size()]; for (int i = 0; i < rings.size(); i++) { linearRings[i] = getLinearRing(rings, i); } return new PolygonImpl(this, linearRings); } } public Polygon polygon(final Polygon polygon) { return polygon.newGeometry(this); } public Polygonal polygonal(final Geometry geometry) { if (geometry instanceof Polygonal) { final Polygonal polygonal = (Polygonal)geometry.convertGeometry(this); return polygonal; } else if (geometry.isGeometryCollection()) { final List<Polygon> polygons = new ArrayList<>(); for (final Geometry part : geometry.geometries()) { if (part instanceof Polygon) { polygons.add((Polygon)part); } else { throw new IllegalArgumentException( "Cannot convert class " + part.getGeometryType() + " to Polygonal\n" + geometry); } } return polygonal(polygons); } else { throw new IllegalArgumentException( "Cannot convert class " + geometry.getGeometryType() + " to Polygonal\n" + geometry); } } public Polygonal polygonal(final Iterable<?> polygons) { final Polygon[] polygonArray = getPolygonArray(polygons); return polygonal(polygonArray); } public Polygonal polygonal(final Object... polygons) { return polygonal(Arrays.asList(polygons)); } /** * Creates a MultiPolygon using the given Polygons; a null or empty array * will Construct a newn empty Polygon. The polygons must conform to the * assertions specified in the <A * HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features * Specification for SQL</A>. * * @param polygons * Polygons, each of which may be empty but not null * @return the created MultiPolygon */ public Polygonal polygonal(final Polygon... polygons) { if (polygons == null || polygons.length == 0) { return polygon(); } else if (polygons.length == 1) { return polygons[0]; } else { return new MultiPolygonImpl(this, polygons); } } /** * Project the geometry if it is in a different coordinate system * * @param geometry * @return */ public <G extends Geometry> G project(final G geometry) { return geometry.convertGeometry(this); } public Punctual punctual(final Geometry... points) { return punctual(Arrays.asList(points)); } public Punctual punctual(final Geometry geometry) { if (geometry instanceof Punctual) { final Punctual punctual = (Punctual)geometry.convertGeometry(this); return punctual; } else if (geometry.isGeometryCollection()) { final List<Point> points = new ArrayList<>(); for (final Geometry part : geometry.geometries()) { if (part instanceof Point) { points.add((Point)part); } else { throw new IllegalArgumentException( "Cannot convert class " + part.getGeometryType() + " to Punctual\n" + geometry); } } return punctual(points); } else { throw new IllegalArgumentException( "Cannot convert class " + geometry.getGeometryType() + " to Punctual\n" + geometry); } } public Punctual punctual(final int axisCount, final double... coordinates) { if (coordinates == null || coordinates.length == 0 || axisCount < 2) { return point(); } else if (coordinates.length % axisCount != 0) { throw new IllegalArgumentException( "Coordinates length=" + coordinates.length + " must be a multiple of " + axisCount); } else { final Point[] points = new Point[coordinates.length / axisCount]; for (int i = 0; i < points.length; i++) { final double[] newCoordinates = new double[axisCount]; System.arraycopy(coordinates, i * axisCount, newCoordinates, 0, axisCount); final Point point = point(newCoordinates); points[i] = point; } return punctual(points); } } public Punctual punctual(final Iterable<?> points) { final Point[] pointArray = getPointArray(points); return punctual(pointArray); } /** * Creates a {@link Punctual} using the * points in the given {@link LineString}. * A <code>null</code> or empty LineString creates an empty {@link Point}. * * @param coordinates a LineString (possibly empty), or <code>null</code> * @return a MultiPoint geometry */ public Punctual punctual(final LineString coordinatesList) { if (coordinatesList == null) { return punctual(); } else { final Point[] points = new Point[coordinatesList.getVertexCount()]; for (int i = 0; i < points.length; i++) { final Point coordinates = coordinatesList.getPoint(i); final Point point = point(coordinates); points[i] = point; } return punctual(points); } } /** * Creates a {@link Punctual} using the given {@link Point}s. * A null or empty array will Construct a new empty Point. * * @param coordinates an array (without null elements), or an empty array, or <code>null</code> * @return a {@link Punctual} object */ public Punctual punctual(final Point... points) { if (points == null || points.length == 0) { return point(); } else if (points.length == 1) { return points[0]; } else { return new MultiPointImpl(this, points); } } public GeometryFactory to2dFloating() { return fixed(this.coordinateSystem, this.coordinateSystemId, 2, SCALES_FLOATING_2); } @Override public double toDoubleX(final int x) { return x / this.scaleX; } @Override public double toDoubleY(final int y) { return y / this.scaleY; } @Override public double toDoubleZ(final int z) { return z / this.scaleZ; } @Override public int toIntX(final double x) { if (Double.isFinite(x)) { return (int)Math.round(x * this.scaleZ); } else { return Integer.MIN_VALUE; } } @Override public int toIntY(final double y) { if (Double.isFinite(y)) { return (int)Math.round(y * this.scaleY); } else { return Integer.MIN_VALUE; } } @Override public int toIntZ(final double z) { if (Double.isFinite(z)) { return (int)Math.round(z * this.scaleZ); } else { return Integer.MIN_VALUE; } } @Override public MapEx toMap() { final MapEx map = new LinkedHashMapEx(); addTypeToMap(map, "geometryFactory"); map.put("srid", getCoordinateSystemId()); map.put("axisCount", getAxisCount()); final double scaleX = getScaleX(); addToMap(map, "scaleX", scaleX, 0.0); final double scaleY = getScaleY(); addToMap(map, "scaleY", scaleY, 0.0); if (this.axisCount > 2) { final double scaleZ = getScaleZ(); addToMap(map, "scaleZ", scaleZ, 0.0); } return map; } @Override public String toString() { final StringBuilder string = new StringBuilder(); final int coordinateSystemId = getCoordinateSystemId(); if (this.coordinateSystem != null) { string.append(this.coordinateSystem.getCoordinateSystemName()); string.append(", "); } string.append("coordinateSystemId="); string.append(coordinateSystemId); string.append(", axisCount="); string.append(this.axisCount); string.append(", scales="); string.append(Arrays.toString(this.scales)); return string.toString(); } }