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
}
}