Class PrecisionModel

  1  
  2  
  3 /*
  4  * Copyright (c) 2016 Vivid Solutions.
  5  *
  6  * All rights reserved. This program and the accompanying materials
  7  * are made available under the terms of the Eclipse Public License 2.0
  8  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  9  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 10  * and the Eclipse Distribution License is available at
 11  *
 12  * http://www.eclipse.org/org/documents/edl-v10.php.
 13  */
 14 package org.locationtech.jts.geom;
 15  
 16 import java.io.Serializable;
 17 import java.util.HashMap;
 18 import java.util.Map;
 19  
 20 import org.locationtech.jts.io.WKTWriter;
 21  
 22 /**
 23  * Specifies the precision model of the {@link Coordinate}s in a {@link Geometry}.
 24  * In other words, specifies the grid of allowable
 25  *  points for all <code>Geometry</code>s.
 26  * <p>
 27  * The {@link #makePrecise(Coordinate)} method allows rounding a coordinate to
 28  * a "precise" value; that is, one whose
 29  *  precision is known exactly.
 30  *<p>
 31  * Coordinates are assumed to be precise in geometries.
 32  * That is, the coordinates are assumed to be rounded to the
 33  * precision model given for the geometry.
 34  * JTS input routines automatically round coordinates to the precision model
 35  * before creating Geometries.
 36  * All internal operations
 37  * assume that coordinates are rounded to the precision model.
 38  * Constructive methods (such as boolean operations) always round computed
 39  * coordinates to the appropriate precision model.
 40  * <p>
 41  * Currently three types of precision model are supported:
 42  * <ul>
 43  * <li>FLOATING - represents full double precision floating point.
 44  * This is the default precision model used in JTS
 45  * <li>FLOATING_SINGLE - represents single precision floating point.
 46  * <li>FIXED - represents a model with a fixed number of decimal places.
 47  *  A Fixed Precision Model is specified by a scale factor.
 48  *  The scale factor specifies the size of the grid which numbers are rounded to.
 49  *  Input coordinates are mapped to fixed coordinates according to the following
 50  *  equations:
 51  *    <UL>
 52  *      <LI> jtsPt.x = round( (inputPt.x * scale ) / scale
 53  *      <LI> jtsPt.y = round( (inputPt.y * scale ) / scale
 54  *    </UL>
 55  * </ul>
 56  * For example, to specify 3 decimal places of precision, use a scale factor
 57  * of 1000. To specify -3 decimal places of precision (i.e. rounding to
 58  * the nearest 1000), use a scale factor of 0.001.
 59  * <p>
 60  * Coordinates are represented internally as Java double-precision values.
 61  * Since Java uses the IEEE-394 floating point standard, this
 62  * provides 53 bits of precision. (Thus the maximum precisely representable
 63  * <i>integer</i> is 9,007,199,254,740,992 - or almost 16 decimal digits of precision).
 64  * <p>
 65  * JTS binary methods currently do not handle inputs which have different precision models.
 66  * The precision model of any constructed geometric value is undefined.
 67  *
 68  *@version 1.7
 69  */
 70 public class PrecisionModel implements Serializable, Comparable
 71 {
 72     /**
 73      * Determines which of two {@link PrecisionModel}s is the most precise
 74      * (allows the greatest number of significant digits).
 75      * 
 76      * @param pm1 a PrecisionModel
 77      * @param pm2 a PrecisionModel
 78      * @return the PrecisionModel which is most precise
 79      */
 80     public static PrecisionModel mostPrecise(PrecisionModel pm1, PrecisionModel pm2)
 81     {
 82         if (pm1.compareTo(pm2) >= 0)
 83             return pm1;
 84         return pm2;
 85     }
 86     
 87   private static final long serialVersionUID = 7777263578777803835L;
 88  
 89   /**
 90    * The types of Precision Model which JTS supports.
 91    */
 92   public static class Type
 93       implements Serializable
 94   {
 95     private static final long serialVersionUID = -5528602631731589822L;
 96     private static Map nameToTypeMap = new HashMap();
 97     public Type(String name) {
 98         this.name = name;
 99         nameToTypeMap.put(name, this);
100     }
101     private String name;
102     public String toString() { return name; }
103     
104     
105     /*
106      * Ssee http://www.javaworld.com/javaworld/javatips/jw-javatip122.html
107      */
108     private Object readResolve() {
109         return nameToTypeMap.get(name);
110     }
111   }
112  
113   /**
114    * Fixed Precision indicates that coordinates have a fixed number of decimal places.
115    * The number of decimal places is determined by the log10 of the scale factor.
116    */
117   public static final Type FIXED = new Type("FIXED");
118   /**
119    * Floating precision corresponds to the standard Java
120    * double-precision floating-point representation, which is
121    * based on the IEEE-754 standard
122    */
123   public static final Type FLOATING = new Type("FLOATING");
124   /**
125    * Floating single precision corresponds to the standard Java
126    * single-precision floating-point representation, which is
127    * based on the IEEE-754 standard
128    */
129   public static final Type FLOATING_SINGLE = new Type("FLOATING SINGLE");
130  
131  
132   /**
133    *  The maximum precise value representable in a double. Since IEE754
134    *  double-precision numbers allow 53 bits of mantissa, the value is equal to
135    *  2^53 - 1.  This provides <i>almost</i> 16 decimal digits of precision.
136    */
137   public final static double maximumPreciseValue = 9007199254740992.0;
138  
139   /**
140    * The type of PrecisionModel this represents.
141    */
142   private Type modelType;
143   /**
144    * The scale factor which determines the number of decimal places in fixed precision.
145    */
146   private double scale;
147  
148   /**
149    * Creates a <code>PrecisionModel</code> with a default precision
150    * of FLOATING.
151    */
152   public PrecisionModel() {
153     // default is floating precision
154     modelType = FLOATING;
155   }
156  
157   /**
158    * Creates a <code>PrecisionModel</code> that specifies
159    * an explicit precision model type.
160    * If the model type is FIXED the scale factor will default to 1.
161    *
162    * @param modelType the type of the precision model
163    */
164   public PrecisionModel(Type modelType)
165   {
166     this.modelType = modelType;
167     if (modelType == FIXED)
168     {
169       setScale(1.0);
170     }
171   }
172   /**
173    *  Creates a <code>PrecisionModel</code> that specifies Fixed precision.
174    *  Fixed-precision coordinates are represented as precise internal coordinates,
175    *  which are rounded to the grid defined by the scale factor.
176    *
177    *@param  scale    amount by which to multiply a coordinate after subtracting
178    *      the offset, to obtain a precise coordinate
179    *@param  offsetX  not used.
180    *@param  offsetY  not used.
181    *
182    * @deprecated offsets are no longer supported, since internal representation is rounded floating point
183    */
184   public PrecisionModel(double scale, double offsetX, double offsetY) {
185     modelType = FIXED;
186     setScale(scale);
187   }
188   /**
189    *  Creates a <code>PrecisionModel</code> that specifies Fixed precision.
190    *  Fixed-precision coordinates are represented as precise internal coordinates,
191    *  which are rounded to the grid defined by the scale factor.
192    *
193    *@param  scale    amount by which to multiply a coordinate after subtracting
194    *      the offset, to obtain a precise coordinate
195    */
196   public PrecisionModel(double scale) {
197     modelType = FIXED;
198     setScale(scale);
199   }
200   /**
201    *  Copy constructor to create a new <code>PrecisionModel</code>
202    *  from an existing one.
203    */
204   public PrecisionModel(PrecisionModel pm) {
205     modelType = pm.modelType;
206     scale = pm.scale;
207   }
208  
209  
210   /**
211    * Tests whether the precision model supports floating point
212    * @return <code>true</code> if the precision model supports floating point
213    */
214   public boolean isFloating()
215   {
216     return modelType == FLOATING || modelType == FLOATING_SINGLE;
217   }
218  
219   /**
220    * Returns the maximum number of significant digits provided by this
221    * precision model.
222    * Intended for use by routines which need to print out 
223    * decimal representations of precise values (such as {@link WKTWriter}).
224    * <p>
225    * This method would be more correctly called
226    * <tt>getMinimumDecimalPlaces</tt>, 
227    * since it actually computes the number of decimal places
228    * that is required to correctly display the full
229    * precision of an ordinate value.
230    * <p>
231    * Since it is difficult to compute the required number of
232    * decimal places for scale factors which are not powers of 10,
233    * the algorithm uses a very rough approximation in this case.
234    * This has the side effect that for scale factors which are
235    * powers of 10 the value returned is 1 greater than the true value.
236    * 
237    *
238    * @return the maximum number of decimal places provided by this precision model
239    */
240   public int getMaximumSignificantDigits() {
241     int maxSigDigits = 16;
242     if (modelType == FLOATING) {
243       maxSigDigits = 16;
244     } else if (modelType == FLOATING_SINGLE) {
245       maxSigDigits = 6;
246     } else if (modelType == FIXED) {
247       maxSigDigits = 1 + (int) Math.ceil(Math.log(getScale()) / Math.log(10));
248     }
249     return maxSigDigits;
250   }
251  
252   /**
253    * Returns the scale factor used to specify a fixed precision model.
254    * The number of decimal places of precision is 
255    * equal to the base-10 logarithm of the scale factor.
256    * Non-integral and negative scale factors are supported.
257    * Negative scale factors indicate that the places 
258    * of precision is to the left of the decimal point.  
259    *
260    *@return the scale factor for the fixed precision model
261    */
262   public double getScale() {
263     return scale;
264   }
265  
266   /**
267    * Gets the type of this precision model
268    * @return the type of this precision model
269    * @see Type
270    */
271   public Type getType()
272   {
273     return modelType;
274   }
275   /**
276    *  Sets the multiplying factor used to obtain a precise coordinate.
277    * This method is private because PrecisionModel is an immutable (value) type.
278    */
279   private void setScale(double scale)
280   {
281     this.scale = Math.abs(scale);
282   }
283  
284   /**
285    * Returns the x-offset used to obtain a precise coordinate.
286    *
287    * @return the amount by which to subtract the x-coordinate before
288    *         multiplying by the scale
289    * @deprecated Offsets are no longer used
290    */
291   public double getOffsetX() {
292     //We actually don't use offsetX and offsetY anymore ... [Jon Aquino]
293     return 0;
294   }
295  
296  
297  
298   /**
299    * Returns the y-offset used to obtain a precise coordinate.
300    *
301    * @return the amount by which to subtract the y-coordinate before
302    *         multiplying by the scale
303    * @deprecated Offsets are no longer used
304    */
305   public double getOffsetY() {
306     return 0;
307   }
308  
309   /**
310    *  Sets <code>internal</code> to the precise representation of <code>external</code>.
311    *
312    * @param external the original coordinate
313    * @param internal the coordinate whose values will be changed to the
314    *                 precise representation of <code>external</code>
315    * @deprecated use makePrecise instead
316    */
317   public void toInternal (Coordinate external, Coordinate internal) {
318     if (isFloating()) {
319       internal.x = external.x;
320       internal.y = external.y;
321     }
322     else {
323       internal.x = makePrecise(external.x);
324       internal.y = makePrecise(external.y);
325     }
326     internal.setZ(external.getZ());
327   }
328  
329   /**
330    *  Returns the precise representation of <code>external</code>.
331    *
332    *@param  external  the original coordinate
333    *@return           the coordinate whose values will be changed to the precise
334    *      representation of <code>external</code>
335    * @deprecated use makePrecise instead
336    */
337   public Coordinate toInternal(Coordinate external) {
338     Coordinate internal = new Coordinate(external);
339     makePrecise(internal);
340     return internal;
341   }
342  
343   /**
344    *  Returns the external representation of <code>internal</code>.
345    *
346    *@param  internal  the original coordinate
347    *@return           the coordinate whose values will be changed to the
348    *      external representation of <code>internal</code>
349    * @deprecated no longer needed, since internal representation is same as external representation
350    */
351   public Coordinate toExternal(Coordinate internal) {
352     Coordinate external = new Coordinate(internal);
353     return external;
354   }
355  
356   /**
357    *  Sets <code>external</code> to the external representation of <code>internal</code>.
358    *
359    *@param  internal  the original coordinate
360    *@param  external  the coordinate whose values will be changed to the
361    *      external representation of <code>internal</code>
362    * @deprecated no longer needed, since internal representation is same as external representation
363    */
364   public void toExternal(Coordinate internal, Coordinate external) {
365       external.x = internal.x;
366       external.y = internal.y;
367   }
368  
369   /**
370    * Rounds a numeric value to the PrecisionModel grid.
371    * Asymmetric Arithmetic Rounding is used, to provide
372    * uniform rounding behaviour no matter where the number is
373    * on the number line.
374    * <p>
375    * This method has no effect on NaN values.
376    * <p>
377    * <b>Note:</b> Java's <code>Math#rint</code> uses the "Banker's Rounding" algorithm,
378    * which is not suitable for precision operations elsewhere in JTS.
379    */
380   public double makePrecise(double val) 
381   {
382       // don't change NaN values
383       if (Double.isNaN(val)) return val;
384       
385       if (modelType == FLOATING_SINGLE) {
386           float floatSingleVal = (float) val;
387           return (double) floatSingleVal;
388       }
389       if (modelType == FIXED) {
390             return Math.round(val * scale) / scale;
391 //          return Math.rint(val * scale) / scale;
392       }
393       // modelType == FLOATING - no rounding necessary
394       return val;
395   }
396  
397   /**
398    * Rounds a Coordinate to the PrecisionModel grid.
399    */
400   public void makePrecise(Coordinate coord)
401   {
402     // optimization for full precision
403     if (modelType == FLOATING) return;
404  
405     coord.x = makePrecise(coord.x);
406     coord.y = makePrecise(coord.y);
407     //MD says it's OK that we're not makePrecise'ing the z [Jon Aquino]
408   }
409  
410  
411   public String toString() {
412       String description = "UNKNOWN";
413       if (modelType == FLOATING) {
414           description = "Floating";
415       } else if (modelType == FLOATING_SINGLE) {
416           description = "Floating-Single";
417       } else if (modelType == FIXED) {
418           description = "Fixed (Scale=" + getScale() + ")";
419       }
420       return description;
421   }
422  
423   public boolean equals(Object other) {
424     if (! (other instanceof PrecisionModel)) {
425       return false;
426     }
427     PrecisionModel otherPrecisionModel = (PrecisionModel) other;
428     return modelType == otherPrecisionModel.modelType
429         && scale == otherPrecisionModel.scale;
430   }
431   /**
432    *  Compares this {@link PrecisionModel} object with the specified object for order.
433    * A PrecisionModel is greater than another if it provides greater precision.
434    * The comparison is based on the value returned by the
435    * {@link #getMaximumSignificantDigits} method.
436    * This comparison is not strictly accurate when comparing floating precision models
437    * to fixed models; however, it is correct when both models are either floating or fixed.
438    *
439    *@param  o  the <code>PrecisionModel</code> with which this <code>PrecisionModel</code>
440    *      is being compared
441    *@return    a negative integer, zero, or a positive integer as this <code>PrecisionModel</code>
442    *      is less than, equal to, or greater than the specified <code>PrecisionModel</code>
443    */
444   public int compareTo(Object o) {
445     PrecisionModel other = (PrecisionModel) o;
446  
447     int sigDigits = getMaximumSignificantDigits();
448     int otherSigDigits = other.getMaximumSignificantDigits();
449     return Integer.compare(sigDigits, otherSigDigits);
450 //    if (sigDigits > otherSigDigits)
451 //      return 1;
452 //    else if
453 //    if (modelType == FLOATING && other.modelType == FLOATING) return 0;
454 //    if (modelType == FLOATING && other.modelType != FLOATING) return 1;
455 //    if (modelType != FLOATING && other.modelType == FLOATING) return -1;
456 //    if (modelType == FIXED && other.modelType == FIXED) {
457 //      if (scale > other.scale)
458 //        return 1;
459 //      else if (scale < other.scale)
460 //        return -1;
461 //      else
462 //        return 0;
463 //    }
464 //    Assert.shouldNeverReachHere("Unknown Precision Model type encountered");
465 //    return 0;
466   }
467 }
468  
469  
470