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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new de04f27  Add limited options to `ExtentSelector` for configuring the 
selection criteria.
de04f27 is described below

commit de04f27807d5be3bdb80fdf196496628c20cbc3b
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Dec 1 15:17:20 2021 +0100

    Add limited options to `ExtentSelector` for configuring the selection 
criteria.
---
 .../sis/internal/referencing/ExtentSelector.java   | 99 ++++++++++++++++++++--
 .../internal/referencing/ExtentSelectorTest.java   | 87 ++++++++++++++++---
 2 files changed, 165 insertions(+), 21 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
index 11da980..c95f9fd 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtentSelector.java
@@ -24,6 +24,7 @@ import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.measure.Range;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -58,8 +59,15 @@ import org.apache.sis.measure.Range;
  *       then the first of those candidates is selected.</li>
  * </ol>
  *
+ * <h2>Change of rule order</h2>
+ * The following configuration flags change the order in which above rules are 
applied.
+ *
+ * <ul>
+ *   <li>{@link #alternateOrdering} — whether the time center criterion is 
tested last.</li>
+ * </ul>
+ *
  * <h2>Usage</h2>
- * Example!
+ * Example:
  *
  * {@preformat java
  *     ExtentSelector<Foo> selector = new ExtentSelector<>(areaOfInterest);
@@ -70,7 +78,7 @@ import org.apache.sis.measure.Range;
  * }
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @param  <T>  the type of object to be selected.
  *
@@ -95,6 +103,27 @@ public final class ExtentSelector<T> {
     private Instant minTOI, maxTOI;
 
     /**
+     * Granularity of the time of interest in seconds, or 0 if none.
+     * If non-zero, this this is always positive.
+     *
+     * @see #setTimeGranularity(Duration)
+     * @see #round(Duration)
+     */
+    private long granularity;
+
+    /**
+     * Whether to use an alternate conditions order where the time center 
criterion is tested last.
+     * This flag can be set to {@code true} if the Time Of Interest (TOI) is 
expected to be larger
+     * than the temporal extent of candidate objects, in which case many 
objects may fit in the TOI.
+     * Those candidates may be considered practically equally good regarding 
temporal aspect,
+     * in which case the caller may want to give precedence to geographic area.
+     *
+     * <p>This flag is often used together with {@link 
#setTimeGranularity(Duration)} method
+     * for reducing the preponderance of temporal criteria.</p>
+     */
+    public boolean alternateOrdering;
+
+    /**
      * The best object found so far.
      */
     private T best;
@@ -108,6 +137,7 @@ public final class ExtentSelector<T> {
     /**
      * Duration of the {@linkplain #best} object, or {@code null} if none.
      * This is equivalent to {@link #largestArea} in the temporal domain.
+     * Value is rounded by {@link #round(Duration)}.
      */
     private Duration longestTime;
 
@@ -222,6 +252,48 @@ public final class ExtentSelector<T> {
     }
 
     /**
+     * Sets the temporal granularity of the Time of Interest (TOI). If 
non-null, intersections with TOI
+     * will be rounded to an integer amount of this granularity. This is 
useful if data are expected at
+     * an approximately regular interval (for example one remote sensing image 
per day) and we want to
+     * ignore slight variations in the temporal extent declared for each image.
+     *
+     * <p>This method is often used together with {@link #alternateOrdering} 
flag
+     * for reducing the preponderance of temporal criteria.</p>
+     *
+     * @param  resolution  granularity of the time of interest, or {@code 
null} if none.
+     * @throws IllegalArgumentException if the given resolution is zero or 
negative.
+     */
+    public final void setTimeGranularity(final Duration resolution) {
+        if (resolution == null) {
+            granularity = 0;
+        } else if (resolution.isZero() || resolution.isNegative()) {
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"resolution", resolution));
+        } else {
+            granularity = resolution.getSeconds();
+        }
+    }
+
+    /**
+     * Returns the given duration rounded to the nearest integer amount of 
temporal granularity.
+     * If no granularity has been specified, then this method returns the 
given duration unmodified.
+     */
+    private Duration round(Duration duration) {
+        if (duration != null && !duration.isZero() && granularity != 0) {
+            long t = duration.getSeconds();
+            long n = t / granularity;
+            long r = t % granularity;
+            if (r != 0) {
+                t -= r;
+                if (t == 0 || r >= (granularity >> 1)) {
+                    t += granularity;
+                };
+                duration = Duration.ofSeconds(t);
+            }
+        }
+        return duration;
+    }
+
+    /**
      * Computes a pseudo-distance between the center of given area and of 
{@link #areaOfInterest}.
      * This is <strong>not</strong> a real distance, neither great circle 
distance or rhumb line.
      * May be {@link Double#NaN} if information is unknown.
@@ -274,10 +346,16 @@ public final class ExtentSelector<T> {
     /**
      * Computes the amount of time outside the time of interest (TOI). The 
returned value is always positive
      * because {@code intersection} should always be less than {@code endTime} 
− {@code startTime} duration.
+     * Value is rounded by {@link #round(Duration)}.
+     *
+     * @param  startTime     start time of of the candidate object, or {@code 
null} if none (unbounded).
+     * @param  endTime       end time of the candidate object, or {@code null} 
if none (unbounded).
+     * @param  intersection  duration of the intersection of [{@code 
startTime} … {@code endTime}]
+     *                       with [{@link #minTOI} … {@link #maxTOI}].
      */
-    private static Duration overtime(final Instant startTime, final Instant 
endTime, final Duration intersection) {
+    private Duration overtime(final Instant startTime, final Instant endTime, 
final Duration intersection) {
         return (startTime != null && endTime != null && intersection != null)
-                ? Duration.between(startTime, endTime).minus(intersection) : 
null;
+                ? round(Duration.between(startTime, 
endTime).minus(intersection)) : null;
     }
 
     /**
@@ -349,8 +427,9 @@ public final class ExtentSelector<T> {
          *         // Compute and test criteria.
          *     }
          */
+        final Duration durationRounded = round(duration);
         int comparison, remainingFieldsToCompute = OVERTIME;
-        if (best != null && (comparison = compare(duration, longestTime, -1)) 
<= 0) {
+        if (best != null && (comparison = compare(durationRounded, 
longestTime, -1)) <= 0) {
             if (comparison != 0) return;
             /*
              * Criterion #2: select the object having smallest amount of time 
outside Time Of Interest (TOI).
@@ -362,10 +441,11 @@ public final class ExtentSelector<T> {
                 if (comparison != 0) return;
                 /*
                  * Criterion #3: select the object having median time closest 
to TOI median time.
+                 * This condition is skipped in the "alternate condition 
ordering" mode.
                  */
                 remainingFieldsToCompute = OUTSIDE_AREA;
                 final double td = temporalDistance(startTime, endTime);
-                if ((comparison = compare(td, temporalDistance, +1)) >= 0) {
+                if (alternateOrdering || (comparison = compare(td, 
temporalDistance, +1)) >= 0) {
                     if (comparison != 0) return;
                     /*
                      * Criterion #4: select the object covering largest 
geographic area.
@@ -383,22 +463,27 @@ public final class ExtentSelector<T> {
                             /*
                              * Criterion #5: select the object having center 
closest to AOI center.
                              * Distances are computed with inexact formulas 
(not a real distance).
+                             * TOI is also compared here in "alternate 
condition ordering" mode.
                              */
                             remainingFieldsToCompute = NONE;
                             final double pd = pseudoDistance(bbox);
                             if (compare(pd, pseudoDistance, +1) >= 0) {
                                 return;
                             }
+                            if (alternateOrdering && compare(td, 
temporalDistance, +1) >= 0) {
+                                return;
+                            }
                             pseudoDistance = pd;
                         }
                         outsideArea = out;
                     }
+                    // largestArea = area; assigned below because was computed 
early.
                 }
                 temporalDistance = td;
             }
             overtime = et;
         }
-        longestTime = duration;
+        longestTime = durationRounded;
         largestArea = area;
         switch (remainingFieldsToCompute) {           // Intentional 
fallthrough in every cases.
             case OVERTIME:          overtime          = overtime(startTime, 
endTime, duration);
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
index 219094f..6d54eaa 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ExtentSelectorTest.java
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.referencing;
 
 import java.util.Date;
+import java.time.Duration;
 import org.opengis.metadata.extent.Extent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
@@ -32,12 +33,24 @@ import static org.junit.Assert.*;
  * Tests {@link ExtentSelector}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
 public final strictfp class ExtentSelectorTest extends TestCase {
     /**
+     * Whether to test an alternate ordering where distance to TOI is tested 
last.
+     *
+     * @see ExtentSelector#alternateOrdering
+     */
+    private boolean alternateOrdering;
+
+    /**
+     * The temporal granularity of the Time Of Interest (TOI), or {@code null} 
if none.
+     */
+    private Duration granularity;
+
+    /**
      * Tests the selector when intersection with AOI is a sufficient criterion.
      */
     @Test
@@ -76,10 +89,37 @@ public final strictfp class ExtentSelectorTest extends 
TestCase {
     @Test
     @Ignore("Require temporal module, not yet available in SIS.")
     public void testTemporal() {
-        assertBestEquals(time(1000, 2000), 2,
-                         time(1500, 3000),
-                         time(1300, 1800),      // Same duration than above, 
but better centered.
-                         time(1400, 1600));     // Well centered but 
intersection is small.
+        assertBestEquals(time(1000, 2000, true), 2,
+                         time(1500, 2000, true),
+                         time(1300, 1800, false),       // Same duration than 
above, but better centered.
+                         time(1400, 1600, true));       // Well centered but 
intersection is small.
+    }
+
+    /**
+     * Tests using temporal ranges with {@link 
ExtentSelector#alternateOrdering} set to {@code true}.
+     * The criterion should give precedence to larger geographic area instead 
of better centered.
+     */
+    @Test
+    @Ignore("Require temporal module, not yet available in SIS.")
+    public void testAlternateOrdering() {
+        alternateOrdering = true;
+        assertBestEquals(time(1000, 2000, true), 1,
+                         time(1500, 2000, true),        // Not well centered 
by has larger geographic area.
+                         time(1300, 1800, false),       // Better centered but 
smaller geographic area.
+                         time(1400, 1600, true));
+    }
+
+    /**
+     * Tests using temporal ranges with {@link 
ExtentSelector#setTimeGranularity(Duration)} defined.
+     */
+    @Test
+    @Ignore("Require temporal module, not yet available in SIS.")
+    public void testTimeGranularity() {
+        granularity = Duration.ofSeconds(20);
+        assertBestEquals(time(10000, 70000, true), 3,
+                         time(14000, 47000, false),     // 2 units of temporal 
resolution.
+                         time(15000, 46000, true),      // Same size if 
counted in units of temporal resolution.
+                         time(25000, 55000, true));     // Same size in units 
of resolution, but better centered.
     }
 
     /**
@@ -98,11 +138,30 @@ public final strictfp class ExtentSelectorTest extends 
TestCase {
 
     /**
      * Creates an extent for a temporal range having the given boundaries.
+     * A geographic extent is also added, but may be ignored because temporal 
extent has precedence.
+     *
+     * @param startTime  arbitrary start time in milliseconds.
+     * @param endTime    arbitrary end time in milliseconds.
+     * @param largeArea  {@code false} for associating a small geographic area,
+     *                   {@code true} for associating a larger geographic area.
      */
-    private static Extent time(final long startTime, final long endTime) {
+    private static Extent time(final long startTime, final long endTime, final 
boolean largeArea) {
+        final DefaultGeographicBoundingBox bbox = new 
DefaultGeographicBoundingBox(
+                largeArea ? -20 : -10, 10,
+                largeArea ?  10 :  20, 30);
         final DefaultTemporalExtent range = new DefaultTemporalExtent();
         range.setBounds(new Date(startTime), new Date(endTime));
-        return new DefaultExtent(null, null, null, range);
+        return new DefaultExtent(null, bbox, null, range);
+    }
+
+    /**
+     * Creates the selector to use for testing purpose.
+     */
+    private ExtentSelector<Integer> create(final Extent aoi) {
+        ExtentSelector<Integer> selector = new ExtentSelector<>(aoi);
+        selector.alternateOrdering = alternateOrdering;
+        selector.setTimeGranularity(granularity);
+        return selector;
     }
 
     /**
@@ -111,34 +170,34 @@ public final strictfp class ExtentSelectorTest extends 
TestCase {
      * @param aoi       area of interest to give to {@link ExtentSelector} 
constructor.
      * @param expected  expected best result: 1 for <var>a</var>, 2 for 
<var>b</var> or 3 for <var>c</var>.
      */
-    private static void assertBestEquals(final Extent aoi, final Integer 
expected,
-                                         final Extent a, final Extent b, final 
Extent c)
+    private void assertBestEquals(final Extent aoi, final Integer expected,
+                                  final Extent a, final Extent b, final Extent 
c)
     {
-        ExtentSelector<Integer> selector = new ExtentSelector<>(aoi);
+        ExtentSelector<Integer> selector = create(aoi);
         selector.evaluate(a, 1);
         selector.evaluate(b, 2);
         selector.evaluate(c, 3);
         assertEquals(expected, selector.best());
 
-        selector = new ExtentSelector<>(aoi);
+        selector = create(aoi);
         selector.evaluate(b, 2);
         selector.evaluate(c, 3);
         selector.evaluate(a, 1);
         assertEquals(expected, selector.best());
 
-        selector = new ExtentSelector<>(aoi);
+        selector = create(aoi);
         selector.evaluate(c, 3);
         selector.evaluate(a, 1);
         selector.evaluate(b, 2);
         assertEquals(expected, selector.best());
 
-        selector = new ExtentSelector<>(aoi);
+        selector = create(aoi);
         selector.evaluate(a, 1);
         selector.evaluate(c, 3);
         selector.evaluate(b, 2);
         assertEquals(expected, selector.best());
 
-        selector = new ExtentSelector<>(aoi);
+        selector = create(aoi);
         selector.evaluate(b, 2);
         selector.evaluate(a, 1);
         selector.evaluate(c, 3);

Reply via email to