/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2006 Vivid Solutions * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.geometry.iso; import java.io.Serializable; import org.geotools.geometry.iso.topograph2D.Coordinate; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Geometry; import org.opengis.geometry.Precision; import org.opengis.geometry.PrecisionType; /** * Specifies the precision model of the {@link Coordinate}s in a * {@link Geometry}. In other words, specifies the grid of allowable points for * all <code>Geometry</code>s. * <p> * The {@link makePrecise} method allows rounding a coordinate to a "precise" * value; that is, one whose precision is known exactly. * <p> * Coordinates are assumed to be precise in geometries. That is, the coordinates * are assumed to be rounded to the precision model given for the geometry. JTS * input routines automatically round coordinates to the precision model before * creating Geometries. All internal operations assume that coordinates are * rounded to the precision model. Constructive methods (such as boolean * operations) always round computed coordinates to the appropriate precision * model. * <p> * Currently three types of precision model are supported: * <ul> * <li>FLOATING - represents full double precision floating point. This is the * default precision model used in JTS * <li>FLOATING_SINGLE - represents single precision floating point. * <li>FIXED - represents a model with a fixed number of decimal places. A * Fixed Precision Model is specified by a scale factor. The scale factor * specifies the grid which numbers are rounded to. Input coordinates are mapped * to fixed coordinates according to the following equations: * <UL> * <LI> jtsPt.x = round( (inputPt.x * scale ) / scale * <LI> jtsPt.y = round( (inputPt.y * scale ) / scale * </UL> * </ul> * Coordinates are represented internally as Java double-precision values. Since * Java uses the IEEE-394 floating point standard, this provides 53 bits of * precision. (Thus the maximum precisely representable integer is * 9,007,199,254,740,992). * <p> * JTS methods currently do not handle inputs with different precision models. * * * * * @source $URL$ * @version 1.7.2 */ public class PrecisionModel implements Serializable, Precision { private static final long serialVersionUID = 7777263578777803835L; /** * Fixed Precision indicates that coordinates have a fixed number of decimal * places. The number of decimal places is determined by the log10 of the * scale factor. */ public static final PrecisionType FIXED = PrecisionType.FIXED; /** * Floating precision corresponds to the standard Java double-precision * floating-point representation, which is based on the IEEE-754 standard */ public static final PrecisionType FLOATING = PrecisionType.DOUBLE; /** * Floating single precision corresponds to the standard Java * single-precision floating-point representation, which is based on the * IEEE-754 standard */ public static final PrecisionType FLOATING_SINGLE = PrecisionType.FLOAT; /** * The maximum precise value representable in a double. Since IEE754 * double-precision numbers allow 53 bits of mantissa, the value is equal to * 2^53 - 1. This provides <i>almost</i> 16 decimal digits of precision. */ public final static double maximumPreciseValue = 9007199254740992.0; /** * The type of PrecisionModel this represents. */ private PrecisionType modelType; /** * The scale factor which determines the number of decimal places in fixed * precision. */ private double scale; /** * Creates a <code>PrecisionModel</code> with a default precision of * FLOATING. */ public PrecisionModel() { // default is floating precision modelType = FLOATING; } /** * Creates a <code>PrecisionModel</code> that specifies an explicit * precision model type. If the model type is FIXED the scale factor will * default to 1. * * @param modelType * the type of the precision model */ public PrecisionModel(PrecisionType modelType) { this.modelType = modelType; if (modelType == FIXED) { setScale(1.0); } } /** * Creates a <code>PrecisionModel</code> that specifies Fixed precision. * Fixed-precision coordinates are represented as precise internal * coordinates, which are rounded to the grid defined by the scale factor. * * @param scale * amount by which to multiply a coordinate after subtracting the * offset, to obtain a precise coordinate */ public PrecisionModel(double scale) { modelType = FIXED; setScale(scale); } /** * Copy constructor to create a new <code>PrecisionModel</code> from an * existing one. */ public PrecisionModel(PrecisionModel pm) { modelType = pm.modelType; scale = pm.scale; } /** * Tests whether the precision model supports floating point * * @return <code>true</code> if the precision model supports floating * point */ public boolean isFloating() { return modelType == FLOATING || modelType == FLOATING_SINGLE; } /** * Returns the maximum number of significant digits provided by this * precision model. Intended for use by routines which need to print out * precise values. * * @return the maximum number of decimal places provided by this precision * model */ public int getMaximumSignificantDigits() { int maxSigDigits = 16; if (modelType == FLOATING) { maxSigDigits = 16; } else if (modelType == FLOATING_SINGLE) { maxSigDigits = 6; } else if (modelType == FIXED) { maxSigDigits = 1 + (int) Math.ceil(Math.log(getScale()) / Math.log(10)); } return maxSigDigits; } /** * Returns the multiplying factor used to obtain a precise coordinate. This * method is private because PrecisionModel is intended to be an immutable * (value) type. * * @return the amount by which to multiply a coordinate after subtracting * the offset */ public double getScale() { return scale; } /** * Gets the type of this PrecisionModel * * @return the type of this PrecisionModel */ public PrecisionType getType() { return modelType; } /** * Sets the multiplying factor used to obtain a precise coordinate. This * method is private because PrecisionModel is intended to be an immutable * (value) type. * */ private void setScale(double scale) { this.scale = Math.abs(scale); } /** * Rounds a numeric value to the PrecisionModel grid. Asymmetric Arithmetic * Rounding is used, to provide uniform rounding behaviour no matter where * the number is on the number line. * <p> * <b>Note:</b> Java's <code>Math#rint</code> uses the "Banker's * Rounding" algorithm, which is not suitable for precision operations * elsewhere in JTS. */ public double makePrecise(double val) { if (modelType == FLOATING_SINGLE) { float floatSingleVal = (float) val; return (double) floatSingleVal; } if (modelType == FIXED) { return Math.round(val * scale) / scale; // return Math.rint(val * scale) / scale; } // modelType == FLOATING - no rounding necessary return val; } /** * Rounds a Coordinate to the PrecisionModel grid. */ public void makePrecise(Coordinate coord) { // optimization for full precision if (modelType == FLOATING) return; coord.x = makePrecise(coord.x); coord.y = makePrecise(coord.y); // MD says it's OK that we're not makePrecise'ing the z [Jon Aquino] } public String toString() { String description = "UNKNOWN"; if (modelType == FLOATING) { description = "Floating"; } else if (modelType == FLOATING_SINGLE) { description = "Floating-Single"; } else if (modelType == FIXED) { description = "Fixed (Scale=" + getScale() + ")"; } return description; } public boolean equals(Object other) { if (!(other instanceof PrecisionModel)) { return false; } PrecisionModel otherPrecisionModel = (PrecisionModel) other; return modelType == otherPrecisionModel.modelType && scale == otherPrecisionModel.scale; } /** * Compares this {@link PrecisionModel} object with the specified object for * order. A PrecisionModel is greater than another if it provides greater * precision. The comparison is based on the value returned by the * {@link getMaximumSignificantDigits} method. This comparison is not * strictly accurate when comparing floating precision models to fixed * models; however, it is correct when both models are either floating or * fixed. * * @param other * the <code>PrecisionModel</code> with which this * <code>PrecisionModel</code> is being compared * @return a negative integer, zero, or a positive integer as this * <code>PrecisionModel</code> is less than, equal to, or greater * than the specified <code>PrecisionModel</code> */ public int compareTo(PrecisionModel other) { if( other == null ) return 0; int sigDigits = getMaximumSignificantDigits(); int otherSigDigits = other.getMaximumSignificantDigits(); return (new Integer(sigDigits)).compareTo(new Integer(otherSigDigits)); // if (sigDigits > otherSigDigits) // return 1; // else if // if (modelType == FLOATING && other.modelType == FLOATING) return 0; // if (modelType == FLOATING && other.modelType != FLOATING) return 1; // if (modelType != FLOATING && other.modelType == FLOATING) return -1; // if (modelType == FIXED && other.modelType == FIXED) { // if (scale > other.scale) // return 1; // else if (scale < other.scale) // return -1; // else // return 0; // } // Assert.shouldNeverReachHere("Unknown Precision Model type // encountered"); // return 0; } public int compareTo( Precision precision) { if( precision == null ) return 0; int sigDigits = getMaximumSignificantDigits(); int otherSigDigits = ((PrecisionModel) precision).getMaximumSignificantDigits(); return (new Integer(sigDigits)).compareTo(new Integer(otherSigDigits)); } public void round( DirectPosition position ) { if (modelType.isFloating()){ // somekind of optimization return; } double coords[] = position.getCoordinate(); position.setOrdinate( 0, makePrecise( coords[0] )); position.setOrdinate( 1, makePrecise( coords[1] )); if( coords.length == 2 ) return; position.setOrdinate( 2, makePrecise( coords[2] )); if( coords.length == 3 ) return; for( int axis = 3; axis < position.getDimension(); axis++ ){ double ordinate = position.getOrdinate( axis ); ordinate = makePrecise( ordinate ); position.setOrdinate( axis, ordinate ); } } }