You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2020/04/22 17:16:15 UTC

[sis] branch geoapi-4.0 updated (bd836fa -> 0707557)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.


    from bd836fa  Fix a NullPointerException when building a `GridCoverage2D` with domain set to `GridGeometry.UNDEFINED`.
     new 65deba1  Change `GeneralEnvelope.setTimeRange(…)` contract regarding null values in a way more convenient for creating "is before" and "is after" filters. Add some documentation.
     new 0707557  Make `CoordinateFormat` more robust to change of CRS.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/sis/gui/map/StatusBar.java     |   3 +-
 .../gui/referencing/RecentReferenceSystems.java    |  10 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  | 269 ++++++++++++++-------
 .../org/apache/sis/geometry/GeneralEnvelope.java   |  23 +-
 .../apache/sis/referencing/GeodeticCalculator.java |   3 +-
 .../apache/sis/geometry/CoordinateFormatTest.java  |   9 +-
 .../sis/util/collection/FrequencySortedSet.java    |   3 +
 7 files changed, 220 insertions(+), 100 deletions(-)


[sis] 02/02: Make `CoordinateFormat` more robust to change of CRS.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 0707557f01169941fd6aa69dc445dc2f0fedb829
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Apr 22 17:50:21 2020 +0200

    Make `CoordinateFormat` more robust to change of CRS.
---
 .../java/org/apache/sis/gui/map/StatusBar.java     |   3 +-
 .../gui/referencing/RecentReferenceSystems.java    |  10 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  | 269 ++++++++++++++-------
 .../apache/sis/referencing/GeodeticCalculator.java |   3 +-
 .../apache/sis/geometry/CoordinateFormatTest.java  |   9 +-
 5 files changed, 201 insertions(+), 93 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index ae8f08b..5f4abe9 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -51,6 +51,7 @@ import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.portrayal.RenderException;
 import org.apache.sis.internal.util.Strings;
+import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Exceptions;
@@ -371,7 +372,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> {
             sourceCoordinates = targetCoordinates.coordinates;      // Okay to share array if same dimension.
         }
         setDisplayCRS(crs);
-        format.setPrecision(resolution, unit);
+        format.setGroundPrecision(Quantities.create(resolution, unit));
         lastX = lastY = Double.NaN;
     }
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index fd4b0da..cba3feb 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -590,7 +590,7 @@ public class RecentReferenceSystems {
                      */
                     final ObservableList<ReferenceSystem> items = referenceSystems;
                     final ComparisonMode mode = duplicationCriterion.get();
-                    final int count = items.size() - NUM_OTHER_ITEMS;
+                    int count = items.size() - NUM_OTHER_ITEMS;
                     boolean found = false;
                     for (int i=0; i<count; i++) {
                         if (Utilities.deepEquals(newValue, items.get(i), mode)) {
@@ -608,7 +608,13 @@ public class RecentReferenceSystems {
                      */
                     if (!found) {
                         if (count >= NUM_SHOWN_ITEMS) {
-                            items.remove(count - 1);        // Remove the last item before `OTHER`.
+                            final List<ReferenceSystem> selected = getSelectedItems();
+                            for (int i=count; --i >= NUM_CORE_ITEMS;) {
+                                if (!selected.contains(items.get(i))) {         // Do not remove selected items.
+                                    items.remove(i);                            // Remove an item before `OTHER`.
+                                    if (--count < NUM_SHOWN_ITEMS) break;
+                                }
+                            }
                         }
                         items.add(Math.min(items.size(), NUM_CORE_ITEMS), newValue);
                         notifyChanges();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
index 4be8aaf..b1e925d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/CoordinateFormat.java
@@ -32,6 +32,7 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import javax.measure.Unit;
 import javax.measure.UnitConverter;
+import javax.measure.Quantity;
 import javax.measure.quantity.Time;
 import javax.measure.IncommensurableException;
 import org.opengis.geometry.DirectPosition;
@@ -58,6 +59,7 @@ import org.apache.sis.measure.AngleFormat;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.Units;
+import org.apache.sis.measure.Quantities;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.io.CompoundFormat;
 
@@ -79,11 +81,10 @@ import org.apache.sis.io.CompoundFormat;
  * or by overriding the {@link #createFormat(Class)} protected method.
  *
  * <h2>Coordinate reference system</h2>
- * If the Coordinate Reference System (CRS) of the positions to format is known, it should
- * {@linkplain #setDefaultCRS(CoordinateReferenceSystem) be specified} before to invoke other methods in this
- * class because that CRS may impact calculation of properties like the number of fraction digits achieving a
- * {@linkplain #setPrecision(double, Unit) specified precision}.
- * That default CRS is used only if the {@link DirectPosition} does not specify its own CRS.
+ * {@code CoordinateFormat} uses the {@link DirectPosition#getCoordinateReferenceSystem()} value for determining
+ * how to format each coordinate value. If the position does not specify a coordinate reference system, then the
+ * {@linkplain #setDefaultCRS(CoordinateReferenceSystem) default CRS} is assumed. If no default CRS has been
+ * specified, then all coordinates are formatted as decimal numbers.
  *
  * <p>{@code CoordinateFormat} does <strong>not</strong> transform the given coordinates in a unique CRS.
  * If the coordinates need to be formatted in a specific CRS, then the caller should
@@ -122,6 +123,9 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     /**
      * The separator between each coordinate values to be formatted.
      * The default value is a space.
+     *
+     * @see #getSeparator()
+     * @see #setSeparator(String)
      */
     private String separator;
 
@@ -131,6 +135,13 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     private String parseSeparator;
 
     /**
+     * The desired ground precision, or {@code null} if unspecified.
+     *
+     * @see #setGroundPrecision(Quantity)
+     */
+    private Quantity<?> groundPrecision;
+
+    /**
      * The desired precisions for each coordinate, or {@code null} if unspecified.
      * The length of this array does not need to be equal to the number of dimensions;
      * extraneous values are ignored and missing values are assumed equal to 0.
@@ -145,14 +156,29 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     private double[] desiredPrecisions;
 
     /**
+     * Whether the {@link Format} instances have been configured for the precision specified by
+     * {@link #groundPrecision} and {@link #desiredPrecisions}. We use a field separated from
+     * {@link #lastCRS} because precision needs to be set only for formatting, not for parsing.
+     *
+     * @see #setPrecisions(double...)
+     * @see #setGroundPrecision(Quantity)
+     * @see #configure(CoordinateReferenceSystem)
+     */
+    private transient boolean isPrecisionApplied;
+
+    /**
      * The coordinate reference system to assume if no CRS is attached to the position to format.
      * May be {@code null}.
+     *
+     * @see #setDefaultCRS(CoordinateReferenceSystem)
      */
     private CoordinateReferenceSystem defaultCRS;
 
     /**
-     * The coordinate reference system of the last {@link DirectPosition} that we formatted.
+     * The coordinate reference system of the last {@link DirectPosition} that we parsed or formatted.
      * This is used for determining if we need to recompute all other transient fields in this class.
+     *
+     * @see #createFormats(CoordinateReferenceSystem)
      */
     private transient CoordinateReferenceSystem lastCRS;
 
@@ -164,6 +190,8 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     /**
      * The type for each value in the {@code formats} array, or {@code null} if not yet computed.
      * Types are: 0=number, 1=longitude, 2=latitude, 3=other angle, 4=date, 5=elapsed time.
+     *
+     * @see #createFormats(CoordinateReferenceSystem)
      */
     private transient byte[] types;
 
@@ -172,6 +200,8 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
      * not been able to configure the precision. This is the same array than {@link #formats},
      * unless {@link #setPrecisions(double...)} has been invoked.
      * Values at different indices may reference the same {@link Format} instance.
+     *
+     * @see #createFormats(CoordinateReferenceSystem)
      */
     private transient Format[] sharedFormats;
 
@@ -179,6 +209,8 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
      * The formats to use for formatting each coordinate value, or {@code null} if not yet computed.
      * The length of this array should be equal to the number of dimensions in {@link #lastCRS}.
      * Values at different indices may reference the same {@link Format} instance.
+     *
+     * @see #createFormats(CoordinateReferenceSystem)
      */
     private transient Format[] formats;
 
@@ -271,7 +303,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     /**
      * Returns the coordinate reference system to use if no CRS is explicitly associated to a given {@code DirectPosition}.
      * This CRS determines the type of format to use for each coordinate (number, angle or date) and the number of fraction
-     * digits to use for achieving a {@linkplain #setPrecision(double, Unit) specified precision}.
+     * digits to use for achieving a {@linkplain #setGroundPrecision(Quantity) specified precision on ground}.
      *
      * @return the default coordinate reference system, or {@code null} if none.
      */
@@ -291,33 +323,44 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     }
 
     /**
-     * Computes the value of transient fields from the given CRS.
+     * Computes the values of transient fields from the given CRS. The {@link #lastCRS} field is set to the given CRS
+     * for allowing callers to check if this method needs to be invoked again (this method does not check by itself).
+     * This method does not configure the formats for precisions specified by {@link #setPrecisions(double...)} and
+     * related methods; that work is done by {@link #configure(CoordinateReferenceSystem)} at formatting time
+     * (it is not needed at parsing time).
+     *
+     * @param  crs  the CRS for which to create the {@link Format} instances.
+     *
+     * @see #configure(CoordinateReferenceSystem)
      */
-    private void configure(final CoordinateReferenceSystem crs) {
-        types         = null;
-        formats       = null;
-        sharedFormats = null;
-        units         = null;
-        toFormatUnit  = null;
-        unitSymbols   = null;
-        epochs        = null;
-        negate        = 0;
-        lastCRS       = crs;
-        if (crs == null) {
-            return;
-        }
+    private void createFormats(final CoordinateReferenceSystem crs) {
+        types              = null;
+        formats            = null;
+        sharedFormats      = null;
+        units              = null;
+        toFormatUnit       = null;
+        unitSymbols        = null;
+        epochs             = null;
+        negate             = 0L;
+        lastCRS            = crs;
+        isPrecisionApplied = false;
         /*
          * If no CRS were specified, we will format everything as numbers. Working with null CRS
          * is sometime useful because null CRS are allowed in DirectPosition according ISO 19107.
-         * Otherwise (if a CRS is given), infer the format subclasses from the axes.
          */
+        if (crs == null) {
+            return;
+        }
         final CoordinateSystem cs = crs.getCoordinateSystem();
         if (cs == null) {
             return;                                    // Paranoiac check (should never be null).
         }
-        final int dimension = cs.getDimension();
-        final byte[]   types   = new byte  [dimension];
-        final Format[] formats = new Format[dimension];
+        /*
+         * Otherwise (if a CRS is given), infer the format subclasses from the axes.
+         */
+        final int      dimension = cs.getDimension();
+        final byte[]   types     = new byte  [dimension];
+        final Format[] formats   = new Format[dimension];
         for (int i=0; i<dimension; i++) {
             final CoordinateSystemAxis axis = cs.getAxis(i);
             if (axis == null) {                                               // Paranoiac check.
@@ -383,9 +426,9 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
                 }
             }
         }
-        this.types    = types;         // Assign only on success.
+        this.types    = types;          // Assign only on success.
         this.formats  = formats;
-        sharedFormats = formats;
+        sharedFormats = formats;        // `getFormatClone(int)` will separate arrays later if needed.
     }
 
     /**
@@ -461,9 +504,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
      * @since 1.1
      */
     public double[] getPrecisions() {
-        if (lastCRS != defaultCRS) {
-            configure(defaultCRS);
-        }
+        configure(defaultCRS);
         Format[] cf = formats;
         if (cf == null) {
             cf = new Format[DEFAULT_DIMENSION];
@@ -492,21 +533,21 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
      * will be shown with two fraction digits when formatted as decimal numbers, or with "D°MM"
      * pattern when formatted as angles.
      *
-     * <p>This precision does not have a clear relationship to the precision on the ground.
+     * <p>This precision does not have a direct relationship to the precision on the ground.
      * For example a precision of 0.01 could be one centimeter or 10 meters, depending if
      * the units of measurement in that dimension is meter or kilometer.
-     * For a precision related to the ground, use {@link #setPrecision(double, Unit)} instead.</p>
+     * For a precision related to the ground, use {@link #setGroundPrecision(Quantity)} instead.</p>
      *
      * <p>If any value in the given array is 0 or {@link Double#NaN}, then there is a choice:
-     * if {@link #setPrecision(double, Unit)} has been invoked, the ground precision specified to that
+     * if {@link #setGroundPrecision(Quantity)} has been invoked, the precision specified to that
      * method will apply (if possible). Otherwise an implementation-specific default precision is used.
-     * So it is possible to use {@link #setPrecision(double, Unit)} for specifying a precision in "real
-     * world" units and to use this {@code setPrecisions(double...)} method for adjusting the precision
-     * of only the vertical axis for example.</p>
+     * A typical use case is to use {@link #setGroundPrecision(Quantity)} for specifying an horizontal
+     * precision in "real world" units and to use this {@code setPrecisions(double...)} method for adjusting
+     * the precision of the vertical axis only.</p>
      *
      * @param  precisions  desired precision at which to format coordinate values in each dimension
-     *                     (may have 0 values for unspecified precision in some of those dimensions),
-     *                     or {@code null} for restoring the default values.
+     *                     (may have 0 or {@link Double#NaN} values for unspecified precisions in some
+     *                     of those dimensions), or {@code null} for restoring the default values.
      *
      * @see AngleFormat#setPrecision(double, boolean)
      * @see DecimalFormat#setMaximumFractionDigits(int)
@@ -514,20 +555,27 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
      * @since 1.1
      */
     public void setPrecisions(final double... precisions) {
+        /*
+         * Implementation note: this method configures (indirectly through calls to `applyPrecision(int)`)
+         * the formats given in the `formats` array but does not touch the `sharedFormats` array. This is
+         * the opposite of `setGroundPrecision(…)` which performs a more global change that affect formats
+         * in the `sharedFormats` array.
+         */
         if (precisions == null) {
             desiredPrecisions = null;
-            formats = sharedFormats;
+            formats = sharedFormats;        // `getFormatClone(int)` will separate arrays later if needed.
         } else {
-            if (desiredPrecisions == null || desiredPrecisions.length != 0) {
+            if (desiredPrecisions == null || desiredPrecisions.length != precisions.length) {
                 desiredPrecisions = new double[precisions.length];
-                Arrays.fill(desiredPrecisions, Double.NaN);
+                // Initial zero values mean "unspecified".
             }
+            isPrecisionApplied &= (formats != null);
             for (int i=0; i<precisions.length; i++) {
                 double p = Math.abs(precisions[i]);
                 if (!(p < Double.POSITIVE_INFINITY)) p = 0;                 // Use ! for replacing NaN.
                 if (desiredPrecisions[i] != (desiredPrecisions[i] = p)) {
                     // Precision changed. Keep format up to date.
-                    if (formats != null && i < formats.length) {
+                    if (isPrecisionApplied && i < formats.length) {
                         applyPrecision(i);
                     }
                 }
@@ -544,7 +592,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     private void applyPrecision(final int dim) {
         final double precision = desiredPrecisions[dim];
         if (precision > 0) {
-            final Format format = formats[dim];
+            final Format format = formats[dim];                 // Will be cloned below if needed.
             /*
              * Intentionally check the DecimalFormat subtype, not the more generic NumberFormat type,
              * because the calculation below assumes base 10 and assumes that fraction digits are for
@@ -562,13 +610,57 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
     }
 
     /**
+     * Computes the values of transient fields from the given CRS and configure the format precisions.
+     * This method updates the {@link #lastCRS} and {@link #isPrecisionApplied} fields.
+     * This method does nothing if above-cited fields are already up to date.
+     *
+     * @param  crs  the CRS for which to create and configure the {@link Format} instances.
+     *
+     * @see #createFormats(CoordinateReferenceSystem)
+     */
+    private void configure(final CoordinateReferenceSystem crs) {
+        if (lastCRS != crs) {
+            createFormats(crs);             // This method sets the `lastCRS` field.
+        }
+        if (!isPrecisionApplied) {
+            if (groundPrecision != null) {
+                applyGroundPrecision(crs);
+            }
+            if (desiredPrecisions != null) {
+                if (sharedFormats == null) {
+                    formats = sharedFormats = new Format[desiredPrecisions.length];
+                    Arrays.fill(formats, getDefaultFormat());
+                    types = new byte[formats.length];
+                }
+                final int n = Math.min(desiredPrecisions.length, formats.length);
+                for (int i=0; i<n; i++) {
+                    applyPrecision(i);          // Will clone Format instances if needed.
+                }
+            }
+            isPrecisionApplied = true;
+        }
+    }
+
+    /**
+     * @deprecated Renamed {@link #setGroundPrecision(Quantity)}
+     * for avoiding confusion with {@link #setPrecisions(double...)}.
+     *
+     * @param  resolution  the desired resolution.
+     * @param  unit        unit of the desired resolution.
+     *
+     * @since 1.0
+     */
+    @Deprecated
+    public void setPrecision(double resolution, Unit<?> unit) {
+        setGroundPrecision(Quantities.create(resolution, unit));
+    }
+
+    /**
      * Adjusts the number of fraction digits to show in coordinates for achieving the given precision.
      * The {@link NumberFormat} and {@link AngleFormat} are configured for coordinates expressed in the
-     * {@linkplain #getDefaultCRS() default coordinate reference system} defined at the moment this method is invoked.
-     * The number of fraction digits is <em>not</em> updated if a different CRS is specified after this method call
-     * or if the coordinates to format are associated to a different CRS.
+     * coordinate reference system of the position to format.
      *
-     * <p>The given resolution will be converted to the units used by coordinate system axes. For example if a 10 metres
+     * The given resolution will be converted to the units used by coordinate system axes. For example if a 10 metres
      * resolution is specified but the {@linkplain #getDefaultCRS() default CRS} axes use kilometres, then this method
      * converts the resolution to 0.01 kilometre and uses that value for inferring that coordinates should be formatted
      * with 2 fraction digits. If the resolution is specified in an angular units such as degrees, this method uses the
@@ -576,21 +668,41 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
      * computing an equivalent resolution in linear units. For example if the ellipsoid of default CRS is WGS84,
      * then this method considers a resolution of 1 second of angle as equivalent to a resolution of about 31 meters.
      * Conversions work also in the opposite direction (from linear to angular units) and are also used for choosing
-     * which angle fields (degrees, minutes or seconds) to show.</p>
+     * which angle fields (degrees, minutes or seconds) to show.
      *
-     * @param  resolution  the desired resolution.
-     * @param  unit        unit of the desired resolution.
+     * <p>If both {@link #setPrecisions(double...)} and {@code setGroundPrecision(Quantity)} are used,
+     * then the values specified with {@code setPrecisions(…)} have precedence and this ground precision
+     * is used only as a fallback. A typical use case is to specify the ground precision for horizontal
+     * dimensions, then to specify a different precision <var>dz</var> for the vertical axis only with
+     * {@code setPrecisions(NaN, NaN, dz)}.</p>
+     *
+     * @param  precision  the desired precision together with its linear or angular unit.
      *
      * @see DecimalFormat#setMaximumFractionDigits(int)
      * @see AngleFormat#setPrecision(double, boolean)
      *
-     * @since 1.0
+     * @since 1.1
      */
     @SuppressWarnings("null")
-    public void setPrecision(double resolution, Unit<?> unit) {
-        ArgumentChecks.ensureFinite("resolution", resolution);
-        ArgumentChecks.ensureNonNull("unit", unit);
-        resolution = Math.abs(resolution);
+    public void setGroundPrecision(final Quantity<?> precision) {
+        ArgumentChecks.ensureNonNull("precision", precision);
+        groundPrecision = precision;
+        if (isPrecisionApplied) {
+            applyGroundPrecision(lastCRS);
+        }
+    }
+
+    /**
+     * Configures the formats for {@link #groundPrecision} value. Contrarily to {@link #applyPrecision(int)},
+     * this method modifies the default formats provided by {@link #getFormat(Class)}. They are the formats
+     * stored in the {@link #sharedFormats} array. Those formats are used as fallback when the {@link #formats}
+     * array does not provide more specific format.
+     *
+     * @param  crs  the target CRS in the conversion from ground units to CRS units.
+     */
+    private void applyGroundPrecision(final CoordinateReferenceSystem crs) {
+        final double resolution = Math.abs(groundPrecision.getValue().doubleValue());
+        final Unit<?> unit = groundPrecision.getUnit();
         if (Units.isTemporal(unit)) {
             return;                                 // Setting temporal resolution is not yet implemented.
         }
@@ -603,7 +715,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
         Resolution related   = null;
         IncommensurableException error = null;
         if (specified.isAngular || Units.isLinear(unit)) try {
-            related = specified.related(ReferencingUtilities.getEllipsoid(defaultCRS));
+            related = specified.related(ReferencingUtilities.getEllipsoid(crs));
         } catch (IncommensurableException e) {
             error = e;
         }
@@ -613,8 +725,8 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
          * units which result in the finest resolution.
          */
         boolean relatedUsed = false;
-        if (defaultCRS != null) {
-            final CoordinateSystem cs = defaultCRS.getCoordinateSystem();
+        if (crs != null) {
+            final CoordinateSystem cs = crs.getCoordinateSystem();
             if (cs != null) {                                                   // Paranoiac check (should never be null).
                 final int dimension = cs.getDimension();
                 for (int i=0; i<dimension; i++) {
@@ -636,7 +748,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
             }
         }
         if (error != null) {
-            Logging.unexpectedException(Logging.getLogger(Loggers.MEASURE), CoordinateFormat.class, "setPrecision", error);
+            Logging.unexpectedException(Logging.getLogger(Loggers.MEASURE), CoordinateFormat.class, "setGroundPrecision", error);
         }
         specified.setPrecision(this);
         if (relatedUsed) {
@@ -646,9 +758,9 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
 
     /**
      * Desired resolution in a given units, together with methods for converting to the units of a coordinate system axis.
-     * This is a helper class for {@link CoordinateFormat#setPrecision(double, Unit)} implementation. An execution of that
-     * method typically creates two instances of this {@code Resolution} class: one for the resolution in metres and another
-     * one for the resolution in degrees.
+     * This is a helper class for {@link CoordinateFormat#setGroundPrecision(Quantity)} implementation. An execution of
+     * that method typically creates two instances of this {@code Resolution} class: one for the resolution in metres
+     * and another one for the resolution in degrees.
      */
     private static final class Resolution {
         /** The desired resolution in the unit of measurement given by {@link #unit}. */
@@ -726,7 +838,8 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
 
         /**
          * Configures the {@link NumberFormat} or {@link AngleFormat} for a number of fraction digits
-         * sufficient for the given resolution.
+         * sufficient for the given resolution. This method configures the shared formats returned by
+         * {@link #getFormat(Class)}. They are the formats stored in the {@link #sharedFormats} array.
          */
         void setPrecision(final CoordinateFormat owner) {
             final Format format = owner.getFormat(isAngular ? Angle.class : Number.class);
@@ -872,26 +985,12 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
         if (crs == null) {
             crs = defaultCRS;                           // May still be null.
         }
-        if (crs != lastCRS) {
-            configure(crs);
-        }
         /*
-         * Unconditionally configure the formatters for the desired precisions because those precisions
-         * may change for every point. Note that the formatters may not have been created if the CRS is
-         * null (because `configure(…)` does not know which format to use), in which case generic number
-         * formats will be used.
+         * Configure the formatters for the desired precision, which can potentially change for each point.
+         * Note that the formatters may not have been created if the CRS is null (because `createFormats(…)`
+         * does not know which format to use), in which case generic number formats will be used.
          */
-        if (desiredPrecisions != null) {
-            if (sharedFormats == null) {
-                formats = sharedFormats = new Format[desiredPrecisions.length];
-                Arrays.fill(formats, getDefaultFormat());
-                types = new byte[formats.length];
-            }
-            final int n = Math.min(desiredPrecisions.length, formats.length);
-            for (int i=0; i<n; i++) {
-                applyPrecision(i);
-            }
-        }
+        configure(crs);
         /*
          * Standard java.text.Format API can only write into a StringBuffer. If the given Appendable is not a
          * StringBuffer, then we will need to format in a temporary buffer before to copy to the Appendable.
@@ -937,7 +1036,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
                     case LONGITUDE: object = new Longitude (value); break;
                     case LATITUDE:  object = new Latitude  (value); break;
                     case ANGLE:     object = new Angle     (value); break;
-                    case DATE:      object = new Date(Math.round(value) + epochs[i]); break;
+                    case DATE:      object = new Date(Math.addExact(Math.round(value), epochs[i])); break;
                 }
             } else {
                 object = value;
@@ -1005,7 +1104,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
          * If no such CRS has been specified, then we will parse everything as plain numbers.
          */
         if (lastCRS != defaultCRS) {
-            configure(defaultCRS);
+            createFormats(defaultCRS);
         }
         final double[] coordinates;
         Format format;
@@ -1079,7 +1178,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
             if (object instanceof Angle) {
                 value = ((Angle) object).degrees();
             } else if (object instanceof Date) {
-                value = ((Date) object).getTime() - epochs[i];
+                value = Math.subtractExact(((Date) object).getTime(), epochs[i]);
             } else {
                 value = ((Number) object).doubleValue();
             }
@@ -1150,7 +1249,7 @@ public class CoordinateFormat extends CompoundFormat<DirectPosition> {
         final CoordinateFormat clone = (CoordinateFormat) super.clone();
         clone.dummy  = null;
         clone.buffer = null;
-        clone.configure(null);
+        clone.createFormats(null);
         if (desiredPrecisions != null) {
             clone.desiredPrecisions = desiredPrecisions.clone();
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
index 8ed2c3b..4dd9582 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/GeodeticCalculator.java
@@ -36,6 +36,7 @@ import org.opengis.geometry.DirectPosition;
 import org.apache.sis.measure.AngleFormat;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Units;
+import org.apache.sis.measure.Quantities;
 import org.apache.sis.geometry.CoordinateFormat;
 import org.apache.sis.internal.referencing.PositionTransformer;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
@@ -1109,7 +1110,7 @@ public class GeodeticCalculator {
                 final CoordinateFormat pointFormat = new CoordinateFormat(locale, null);
                 pointFormat.setSeparator("\t");      // For distributing coordinate values on different columns.
                 pointFormat.setDefaultCRS(crs);
-                pointFormat.setPrecision(Formulas.LINEAR_TOLERANCE, Units.METRE);
+                pointFormat.setGroundPrecision(Quantities.create(Formulas.LINEAR_TOLERANCE, Units.METRE));
                 final TableAppender table = new TableAppender(buffer, " │ ");
                 table.setCellAlignment(TableAppender.ALIGN_CENTER);
                 table.appendHorizontalSeparator();
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/CoordinateFormatTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/CoordinateFormatTest.java
index d098362..60b1e38 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/CoordinateFormatTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/CoordinateFormatTest.java
@@ -24,6 +24,7 @@ import java.text.ParseException;
 import java.io.IOException;
 import org.opengis.geometry.DirectPosition;
 import org.apache.sis.measure.Angle;
+import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.test.mock.VerticalCRSMock;
@@ -257,16 +258,16 @@ public final strictfp class CoordinateFormatTest extends TestCase {
     }
 
     /**
-     * Tests {@link CoordinateFormat#setPrecision(double, Unit)}.
+     * Tests {@link CoordinateFormat#setGroundPrecision(Quantity)}.
      */
     @Test
-    public void testSetPrecision() {
+    public void testSetGroundPrecision() {
         final CoordinateFormat format = new CoordinateFormat(Locale.FRANCE, null);
         final DirectPosition2D pos = new DirectPosition2D(40.123456789, 9.87654321);
         format.setDefaultCRS(HardCodedCRS.WGS84_φλ);
-        format.setPrecision(0.01, Units.GRAD);
+        format.setGroundPrecision(Quantities.create(0.01, Units.GRAD));
         assertEquals("40°07,4′N 9°52,6′E", format.format(pos));
-        format.setPrecision(0.01, Units.METRE);
+        format.setGroundPrecision(Quantities.create(0.01, Units.METRE));
         assertEquals("40°07′24,4444″N 9°52′35,5556″E", format.format(pos));
     }
 


[sis] 01/02: Change `GeneralEnvelope.setTimeRange(…)` contract regarding null values in a way more convenient for creating "is before" and "is after" filters. Add some documentation.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 65deba1d23660cbdf0b6808d8a4c615db16ff2f5
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Apr 22 17:49:01 2020 +0200

    Change `GeneralEnvelope.setTimeRange(…)` contract regarding null values in a way more convenient for creating "is before" and "is after" filters.
    Add some documentation.
---
 .../org/apache/sis/geometry/GeneralEnvelope.java   | 23 +++++++++++++++-------
 .../sis/util/collection/FrequencySortedSet.java    |  3 +++
 2 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
index 6d7d026..fed4233 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
@@ -469,19 +469,28 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable, Seriali
      * floating point values using {@link org.apache.sis.referencing.crs.DefaultTemporalCRS},
      * then delegates to {@link #setRange(int, double, double)}.
      *
-     * Beware that any unspecified time will be convered to {@link Double#NaN} value, therefore invalidating the time
-     * range.
-     *
-     * @param  startTime  the lower temporal value, or {@code null} if unspecified.
-     * @param  endTime    the upper temporal value, or {@code null} if unspecified.
-     * @return whether the temporal component has been set.
+     * <p>Null value means no time limit. More specifically
+     * null {@code startTime} is mapped to {@linkplain Double#NEGATIVE_INFINITY −∞} and
+     * null {@code endTime}   is mapped to {@linkplain Double#POSITIVE_INFINITY +∞}.
+     * This rule makes easy to create <cite>is before</cite> or <cite>is after</cite> temporal filters,
+     * which can be combined with other envelopes using {@linkplain #intersect(Envelope) intersection}
+     * for logical AND, or {@linkplain #add(Envelope) union} for logical OR operations.</p>
+     *
+     * @param  startTime  the lower temporal value, or {@code null} if unbounded.
+     * @param  endTime    the upper temporal value, or {@code null} if unbounded.
+     * @return whether the temporal component has been set, or {@code false}
+     *         if no temporal dimension has been found in this envelope.
      *
      * @since 1.0
      */
     public boolean setTimeRange(final Instant startTime, final Instant endTime) {
         final TemporalAccessor t = TemporalAccessor.of(crs, 0);
         if (t != null) {
-            setRange(t.dimension, t.timeCRS.toValue(startTime), t.timeCRS.toValue(endTime));
+            double lower = t.timeCRS.toValue(startTime);
+            double upper = t.timeCRS.toValue(endTime);
+            if (Double.isNaN(lower)) lower = Double.NEGATIVE_INFINITY;
+            if (Double.isNaN(upper)) upper = Double.POSITIVE_INFINITY;
+            setRange(t.dimension, lower, upper);
             return true;
         } else {
             return false;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/collection/FrequencySortedSet.java b/core/sis-utility/src/main/java/org/apache/sis/util/collection/FrequencySortedSet.java
index 3396c34..b75137e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/collection/FrequencySortedSet.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/collection/FrequencySortedSet.java
@@ -60,6 +60,8 @@ public class FrequencySortedSet<E> extends AbstractSet<E> implements SortedSet<E
     /**
      * The frequency of occurrence for each element. We must use a linked hash map instead of an ordinary
      * hash map because we want to preserve insertion order for elements that occur at the same frequency.
+     * Values are positives if this set sorts by increasing frequencies, or negatives if this set sorts by
+     * decreasing frequencies.
      */
     private final Map<E,Integer> count;
 
@@ -153,6 +155,7 @@ public class FrequencySortedSet<E> extends AbstractSet<E> implements SortedSet<E
             sorted = null;
             occurrence ^= order;
             return count.merge(element, occurrence, (old, n) -> Math.addExact(old, n) - order) == occurrence;
+            // Note: the subtraction by `order` can not overflow.
         }
         return false;
     }