using System; using GeoAPI.Coordinates; using GeoAPI.Geometries; namespace NetTopologySuite.Coordinates { /// /// Specifies the precision model of the s /// in a . /// /// /// /// The method allows for rounding a coordinate to a "precise" value; /// that is, one whose precision is known exactly. Coordinates are assumed to be precise in /// geometries; the coordinates are assumed to be rounded to the precision model given for the /// geometry. NTS factory routines automatically round /// coordinates to the precision model before creating geometries. All internal operations /// assume that coordinates are rounded to the precision model. Generative methods /// (such as boolean operations) always round computed coordinates to the appropriate /// precision model. /// /// /// Currently three types of precision model are supported: /// /// /// /// /// Represents full double precision floating point. This is the default /// precision model used in NTS. /// /// /// /// /// /// Represents single precision floating point. /// /// /// /// /// /// Represents a model with a fixed number of decimal places. A fixed /// 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 /// (known as arithmetic asymmetric rounding, since it moves away from zero if positive, /// and towards zero if negative): /// /// Double x = Math.Floor((inputPt.X * scale) + 0.5) / scale /// Double y = Math.Floor((inputPt.Y * scale) + 0.5) / scale /// /// /// /// /// Coordinates are represented internally as a value. /// Since the CLI uses the IEEE-754 floating point standard, this provides 53 bits of precision. /// (Thus the maximum precisely representable integer is 9,007,199,254,740,992). /// NTS methods currently do not handle inputs with different precision models. /// /// [Serializable] public class PrecisionModel : IPrecisionModel { private const Int32 FloatingPrecisionDigits = 16; private const Int32 FloatingSinglePrecisionDigits = 6; private const Int32 FixedPrecisionDigits = 1; /// /// The maximum precise value representable in a Double. Since IEEE-754 /// Double-precision numbers allow 53 bits of significand, the value is equal to /// 2^53 - 1. This provides almost 16 decimal digits of precision. /// public const Double MaximumPreciseValue = 9007199254740992.0; //public static Boolean operator ==(PrecisionModel left, PrecisionModel right) //{ // return Equals(left, right); //} //public static Boolean operator !=(PrecisionModel left, PrecisionModel right) //{ // return !(left == right); //} private readonly ICoordinateFactory _coordFactory; private readonly PrecisionModelType _modelType; private readonly Double _scale; /// /// Creates a with a default precision /// of . /// /// /// The coordinate factory to use to creat coordinates. /// public PrecisionModel(ICoordinateFactory coordinateFactory) : this(coordinateFactory, PrecisionModelType.DoubleFloating) { } /// /// Creates a that specifies /// an explicit precision model type. /// If the model type is Fixed the scale factor will default to 1. /// /// /// The coordinate factory to use to creat coordinates. /// /// /// The type of the precision model. /// public PrecisionModel(ICoordinateFactory coordinateFactory, PrecisionModelType modelType) { _coordFactory = coordinateFactory; _modelType = modelType; if (modelType == PrecisionModelType.Fixed) { _scale = 1.0; } } /// /// Creates a that specifies Fixed precision. /// /// /// The coordinate factory to use to creat coordinates. /// /// /// Amount by which to multiply a coordinate after subtracting /// the offset, to obtain a precise coordinate. /// /// /// Fixed-precision coordinates are represented as precise internal coordinates, /// which are rounded to the grid defined by the scale factor. /// public PrecisionModel(ICoordinateFactory coordinateFactory, Double scale) :this(coordinateFactory, PrecisionModelType.Fixed) { _scale = Math.Abs(scale); } /// /// Copy constructor to create a new /// from an existing one. /// /// /// The coordinate factory to use to creat coordinates. /// /// The to copy. public PrecisionModel(ICoordinateFactory coordinateFactory, IPrecisionModel pm) { _coordFactory = coordinateFactory; _modelType = pm == null ? PrecisionModelType.DoubleFloating : pm.PrecisionModelType; _scale = pm == null ? 1.0 : pm.Scale; } /// /// Copy constructor to create a new /// from an existing one. /// /// The precision model to copy. public PrecisionModel(PrecisionModel pm) { _coordFactory = pm._coordFactory; _modelType = pm._modelType; _scale = pm._scale; } public ICoordinateFactory CoordinateFactory { get { return _coordFactory; } } public Double Scale { get { return _scale; } } public PrecisionModelType PrecisionModelType { get { return _modelType; } } #region IPrecisionModel Members public Boolean IsFloating { get { return _modelType == PrecisionModelType.DoubleFloating || _modelType == PrecisionModelType.SingleFloating; } } public Double MakePrecise(Double val) { switch (_modelType) { case PrecisionModelType.DoubleFloating: return val; // modelType == FLOATING - no rounding necessary case PrecisionModelType.SingleFloating: Single floatSingleVal = (Single)val; return floatSingleVal; case PrecisionModelType.Fixed: // return Math.Round(val * scale) / scale; // [dguidi] I implemented the Java Math.Round algorithm (used since JTS 1.6). // Java's Math.Rint method, used previous to JTS 1.6, was // the same as the default .Net Math.Round algorithm - // "Banker's Rounding" (ASTM E-29) // [codekaizen] Investigated using the symmetric rounding mode which is also available via // Math.Round(val * _scale, MidpointRounding.AwayFromZero) / scale; // however, I can't tell if symmetric would cause any more problems // than asymmetric arithmetic rounding. return Math.Floor(((val * _scale) + 0.5d)) / _scale; default: throw new InvalidOperationException( "Unknown precision model type: " + _modelType); } } public Int32 MaximumSignificantDigits { get { switch (_modelType) { case PrecisionModelType.DoubleFloating: return FloatingPrecisionDigits; case PrecisionModelType.SingleFloating: return FloatingSinglePrecisionDigits; case PrecisionModelType.Fixed: return FixedPrecisionDigits + (Int32)Math.Ceiling(Math.Log(Scale) / Math.Log(10)); default: throw new InvalidOperationException( "Unknown precision model type: " + _modelType); } } } #endregion #region IPrecisionModel Members public BufferedCoordinate MakePrecise(BufferedCoordinate coord) { // optimization for full precision if (_modelType == PrecisionModelType.DoubleFloating) { return coord; } Double x = MakePrecise(coord[Ordinates.X]); Double y = MakePrecise(coord[Ordinates.Y]); // MD says it's OK that we're not makePrecise'ing the z [Jon Aquino] // TODO: codekaizen - reevaluate making Z precise for 3D return _coordFactory.Create(x, y); } #endregion public override string ToString() { switch (_modelType) { case PrecisionModelType.DoubleFloating: return "Floating"; case PrecisionModelType.SingleFloating: return "Floating-Single"; case PrecisionModelType.Fixed: return "Fixed (Scale = " + Scale + ")"; default: return "Unknown"; } } public Boolean Equals(IPrecisionModel other) { if (other == null) { return false; } return _modelType == other.PrecisionModelType && _scale == other.Scale; } #region IComparable> Members /// /// Compares this object with the /// specified object for order. /// /// /// The with which this /// is being compared. /// /// /// A is greater than another if it /// provides greater precision. The comparison is based on the value returned by /// . /// 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. /// /// /// A negative integer, zero, or a positive integer as this /// is less than, equal to, /// or greater than the specified . /// public Int32 CompareTo(IPrecisionModel other) { return (this as IComparable).CompareTo(other); } #endregion #region IEquatable Members public Boolean Equals(IPrecisionModel other) { throw new NotImplementedException(); } #endregion #region Explicit IPrecisionModel Members ICoordinateFactory IPrecisionModel.CoordinateFactory { get { return _coordFactory; } } ICoordinate IPrecisionModel.MakePrecise(ICoordinate coord) { return MakePrecise(_coordFactory.Create(coord)); } #endregion #region IComparable Members Int32 IComparable.CompareTo(IPrecisionModel other) { if (other == null) { throw new ArgumentNullException("other"); } Int32 significantDigits = MaximumSignificantDigits; Int32 otherSignificantDigits = other.MaximumSignificantDigits; return (significantDigits).CompareTo(otherSignificantDigits); } #endregion } }